diff --git a/dev/player.html b/dev/player.html
index f10bd9d..1752009 100644
--- a/dev/player.html
+++ b/dev/player.html
@@ -53,44 +53,6 @@ if (!(await audioTrack?.canDecode())) {
audioTrack = null;
}
-
-//const startTime2 = audioContext.currentTime + 0.2;
-//const sink = new Metamuxer.AudioDataSink(audioTrack);
-//for await (const { data } of sink.data(0)) {
-// const sample = new Metamuxer.AudioSample(data)
-//
-// console.log(sample.toAudioData())
-//
-// const audioBuffer = sample.toAudioBuffer()
-//
-// //console.log(sample, sample.allocationSize({ planeIndex: 7, format: 'u8-planar' }))
-//
-// //break;
-//
-// /*
-// const audioBuffer = new AudioBuffer({
-// numberOfChannels: sample.numberOfChannels,
-// length: sample.numberOfFrames,
-// sampleRate: sample.sampleRate,
-// });
-//
-// // All user agents are required to support conversion to f32-planar
-// const dataBytes = new Uint8Array(sample.allocationSize({ planeIndex: 0, format: 'u8-planar' }));
-//
-// for (let i = 0; i < sample.numberOfChannels; i++) {
-// sample.copyTo(dataBytes, { planeIndex: i, format: 'u8-planar' });
-// audioBuffer.copyToChannel(new Float32Array(dataBytes).map(x => (x - 128)/128), i);
-// }
-// */
-//
-// const node = audioContext.createBufferSource();
-// node.buffer = audioBuffer;
-// node.connect(audioContext.destination);
-// node.start(startTime2 + sample.timestamp);
-//}
-//
-//await new Promise(() => {})
-
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
let videoRotation = 0;
diff --git a/package-lock.json b/package-lock.json
index 3116aa6..35a1f14 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,23 +13,24 @@
"@types/dom-webcodecs": "^0.1.13"
},
"devDependencies": {
- "@eslint/js": "^9.16.0",
- "@microsoft/api-extractor": "^7.48.0",
- "@stylistic/eslint-plugin": "^2.11.0",
- "esbuild": "^0.23.1",
- "eslint": "^9.16.0",
- "typescript": "^5.5.4",
- "typescript-eslint": "^8.16.0"
+ "@eslint/js": "^9.22.0",
+ "@microsoft/api-extractor": "^7.52.1",
+ "@stylistic/eslint-plugin": "^4.2.0",
+ "esbuild": "^0.25.1",
+ "eslint": "^9.22.0",
+ "typescript": "^5.8.2",
+ "typescript-eslint": "^8.26.1"
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
- "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
+ "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
"cpu": [
"ppc64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"aix"
@@ -39,13 +40,14 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
- "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
+ "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
@@ -55,13 +57,14 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
- "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
+ "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
@@ -71,13 +74,14 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
- "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
+ "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
@@ -87,13 +91,14 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
- "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
+ "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -103,13 +108,14 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
- "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
+ "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -119,13 +125,14 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
- "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
+ "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"freebsd"
@@ -135,13 +142,14 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
- "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
+ "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"freebsd"
@@ -151,13 +159,14 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
- "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
+ "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -167,13 +176,14 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
- "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
+ "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -183,13 +193,14 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
- "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
+ "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
"cpu": [
"ia32"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -199,13 +210,14 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
- "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
+ "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
"cpu": [
"loong64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -215,13 +227,14 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
- "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
+ "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
"cpu": [
"mips64el"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -231,13 +244,14 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
- "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
+ "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
"cpu": [
"ppc64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -247,13 +261,14 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
- "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
+ "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
"cpu": [
"riscv64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -263,13 +278,14 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
- "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
+ "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
"cpu": [
"s390x"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -279,13 +295,14 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
- "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
+ "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -294,14 +311,32 @@
"node": ">=18"
}
},
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz",
+ "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
- "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
+ "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"netbsd"
@@ -311,13 +346,14 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
- "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz",
+ "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"openbsd"
@@ -327,13 +363,14 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
- "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
+ "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"openbsd"
@@ -343,13 +380,14 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
- "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
+ "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"sunos"
@@ -359,13 +397,14 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
- "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
+ "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -375,13 +414,14 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
- "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
+ "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
"cpu": [
"ia32"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -391,13 +431,14 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
- "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
+ "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -446,12 +487,13 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.19.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz",
- "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==",
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz",
+ "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==",
"dev": true,
+ "license": "Apache-2.0",
"dependencies": {
- "@eslint/object-schema": "^2.1.4",
+ "@eslint/object-schema": "^2.1.6",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
@@ -464,6 +506,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -471,20 +514,35 @@
"node": "*"
}
},
- "node_modules/@eslint/core": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz",
- "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==",
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz",
+ "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==",
"dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
+ "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/eslintrc": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
- "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz",
+ "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
@@ -508,6 +566,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -523,13 +582,15 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -538,29 +599,33 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.16.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz",
- "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==",
+ "version": "9.22.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz",
+ "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/object-schema": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
- "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz",
- "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==",
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz",
+ "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==",
"dev": true,
+ "license": "Apache-2.0",
"dependencies": {
+ "@eslint/core": "^0.12.0",
"levn": "^0.4.1"
},
"engines": {
@@ -616,10 +681,11 @@
}
},
"node_modules/@humanwhocodes/retry": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
- "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
+ "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
@@ -629,64 +695,55 @@
}
},
"node_modules/@microsoft/api-extractor": {
- "version": "7.48.0",
- "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.48.0.tgz",
- "integrity": "sha512-FMFgPjoilMUWeZXqYRlJ3gCVRhB7WU/HN88n8OLqEsmsG4zBdX/KQdtJfhq95LQTQ++zfu0Em1LLb73NqRCLYQ==",
+ "version": "7.52.1",
+ "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.1.tgz",
+ "integrity": "sha512-m3I5uAwE05orsu3D1AGyisX5KxsgVXB+U4bWOOaX/Z7Ftp/2Cy41qsNhO6LPvSxHBaapyser5dVorF1t5M6tig==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@microsoft/api-extractor-model": "7.30.0",
+ "@microsoft/api-extractor-model": "7.30.4",
"@microsoft/tsdoc": "~0.15.1",
"@microsoft/tsdoc-config": "~0.17.1",
- "@rushstack/node-core-library": "5.10.0",
+ "@rushstack/node-core-library": "5.12.0",
"@rushstack/rig-package": "0.5.3",
- "@rushstack/terminal": "0.14.3",
- "@rushstack/ts-command-line": "4.23.1",
+ "@rushstack/terminal": "0.15.1",
+ "@rushstack/ts-command-line": "4.23.6",
"lodash": "~4.17.15",
"minimatch": "~3.0.3",
"resolve": "~1.22.1",
"semver": "~7.5.4",
"source-map": "~0.6.1",
- "typescript": "5.4.2"
+ "typescript": "5.8.2"
},
"bin": {
"api-extractor": "bin/api-extractor"
}
},
"node_modules/@microsoft/api-extractor-model": {
- "version": "7.30.0",
- "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.0.tgz",
- "integrity": "sha512-26/LJZBrsWDKAkOWRiQbdVgcfd1F3nyJnAiJzsAgpouPk7LtOIj7PK9aJtBaw/pUXrkotEg27RrT+Jm/q0bbug==",
+ "version": "7.30.4",
+ "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.4.tgz",
+ "integrity": "sha512-RobC0gyVYsd2Fao9MTKOfTdBm41P/bCMUmzS5mQ7/MoAKEqy0FOBph3JOYdq4X4BsEnMEiSHc+0NUNmdzxCpjA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@microsoft/tsdoc": "~0.15.1",
"@microsoft/tsdoc-config": "~0.17.1",
- "@rushstack/node-core-library": "5.10.0"
- }
- },
- "node_modules/@microsoft/api-extractor/node_modules/typescript": {
- "version": "5.4.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
- "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
- "dev": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
+ "@rushstack/node-core-library": "5.12.0"
}
},
"node_modules/@microsoft/tsdoc": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
"integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@microsoft/tsdoc-config": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz",
"integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@microsoft/tsdoc": "0.15.1",
"ajv": "~8.12.0",
@@ -699,6 +756,7 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
@@ -712,6 +770,7 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">= 8"
}
@@ -721,6 +780,7 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
@@ -730,15 +790,16 @@
}
},
"node_modules/@rushstack/node-core-library": {
- "version": "5.10.0",
- "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.10.0.tgz",
- "integrity": "sha512-2pPLCuS/3x7DCd7liZkqOewGM0OzLyCacdvOe8j6Yrx9LkETGnxul1t7603bIaB8nUAooORcct9fFDOQMbWAgw==",
+ "version": "5.12.0",
+ "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.12.0.tgz",
+ "integrity": "sha512-QSwwzgzWoil1SCQse+yCHwlhRxNv2dX9siPnAb9zR/UmMhac4mjMrlMZpk64BlCeOFi1kJKgXRkihSwRMbboAQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"ajv": "~8.13.0",
"ajv-draft-04": "~1.0.0",
"ajv-formats": "~3.0.1",
- "fs-extra": "~7.0.1",
+ "fs-extra": "~11.3.0",
"import-lazy": "~4.0.0",
"jju": "~1.4.0",
"resolve": "~1.22.1",
@@ -758,6 +819,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz",
"integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"json-schema-traverse": "^1.0.0",
@@ -780,12 +842,13 @@
}
},
"node_modules/@rushstack/terminal": {
- "version": "0.14.3",
- "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.14.3.tgz",
- "integrity": "sha512-csXbZsAdab/v8DbU1sz7WC2aNaKArcdS/FPmXMOXEj/JBBZMvDK0+1b4Qao0kkG0ciB1Qe86/Mb68GjH6/TnMw==",
+ "version": "0.15.1",
+ "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.1.tgz",
+ "integrity": "sha512-3vgJYwumcjoDOXU3IxZfd616lqOdmr8Ezj4OWgJZfhmiBK4Nh7eWcv8sU8N/HdzXcuHDXCRGn/6O2Q75QvaZMA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@rushstack/node-core-library": "5.10.0",
+ "@rushstack/node-core-library": "5.12.0",
"supports-color": "~8.1.1"
},
"peerDependencies": {
@@ -798,24 +861,26 @@
}
},
"node_modules/@rushstack/ts-command-line": {
- "version": "4.23.1",
- "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.23.1.tgz",
- "integrity": "sha512-40jTmYoiu/xlIpkkRsVfENtBq4CW3R4azbL0Vmda+fMwHWqss6wwf/Cy/UJmMqIzpfYc2OTnjYP1ZLD3CmyeCA==",
+ "version": "4.23.6",
+ "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.23.6.tgz",
+ "integrity": "sha512-7WepygaF3YPEoToh4MAL/mmHkiIImQq3/uAkQX46kVoKTNOOlCtFGyNnze6OYuWw2o9rxsyrHVfIBKxq/am2RA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@rushstack/terminal": "0.14.3",
+ "@rushstack/terminal": "0.15.1",
"@types/argparse": "1.0.38",
"argparse": "~1.0.9",
"string-argv": "~0.3.1"
}
},
"node_modules/@stylistic/eslint-plugin": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.11.0.tgz",
- "integrity": "sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.2.0.tgz",
+ "integrity": "sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/utils": "^8.13.0",
+ "@typescript-eslint/utils": "^8.23.0",
"eslint-visitor-keys": "^4.2.0",
"espree": "^10.3.0",
"estraverse": "^5.3.0",
@@ -825,7 +890,7 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"peerDependencies": {
- "eslint": ">=8.40.0"
+ "eslint": ">=9.0.0"
}
},
"node_modules/@stylistic/eslint-plugin/node_modules/picomatch": {
@@ -844,7 +909,8 @@
"version": "1.0.38",
"resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz",
"integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@types/dom-mediacapture-transform": {
"version": "0.1.10",
@@ -872,20 +938,21 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz",
- "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==",
+ "version": "8.26.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz",
+ "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.16.0",
- "@typescript-eslint/type-utils": "8.16.0",
- "@typescript-eslint/utils": "8.16.0",
- "@typescript-eslint/visitor-keys": "8.16.0",
+ "@typescript-eslint/scope-manager": "8.26.1",
+ "@typescript-eslint/type-utils": "8.26.1",
+ "@typescript-eslint/utils": "8.26.1",
+ "@typescript-eslint/visitor-keys": "8.26.1",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
- "ts-api-utils": "^1.3.0"
+ "ts-api-utils": "^2.0.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -896,24 +963,21 @@
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
- "eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz",
- "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==",
+ "version": "8.26.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz",
+ "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.16.0",
- "@typescript-eslint/types": "8.16.0",
- "@typescript-eslint/typescript-estree": "8.16.0",
- "@typescript-eslint/visitor-keys": "8.16.0",
+ "@typescript-eslint/scope-manager": "8.26.1",
+ "@typescript-eslint/types": "8.26.1",
+ "@typescript-eslint/typescript-estree": "8.26.1",
+ "@typescript-eslint/visitor-keys": "8.26.1",
"debug": "^4.3.4"
},
"engines": {
@@ -924,22 +988,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz",
- "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==",
+ "version": "8.26.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz",
+ "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.16.0",
- "@typescript-eslint/visitor-keys": "8.16.0"
+ "@typescript-eslint/types": "8.26.1",
+ "@typescript-eslint/visitor-keys": "8.26.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -950,15 +1011,16 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz",
- "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==",
+ "version": "8.26.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz",
+ "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.16.0",
- "@typescript-eslint/utils": "8.16.0",
+ "@typescript-eslint/typescript-estree": "8.26.1",
+ "@typescript-eslint/utils": "8.26.1",
"debug": "^4.3.4",
- "ts-api-utils": "^1.3.0"
+ "ts-api-utils": "^2.0.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -968,19 +1030,16 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz",
- "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==",
+ "version": "8.26.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz",
+ "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -990,19 +1049,20 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz",
- "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==",
+ "version": "8.26.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz",
+ "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.16.0",
- "@typescript-eslint/visitor-keys": "8.16.0",
+ "@typescript-eslint/types": "8.26.1",
+ "@typescript-eslint/visitor-keys": "8.26.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
- "ts-api-utils": "^1.3.0"
+ "ts-api-utils": "^2.0.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1011,10 +1071,8 @@
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
@@ -1022,6 +1080,7 @@
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -1031,6 +1090,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -1042,10 +1102,11 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"dev": true,
+ "license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -1054,15 +1115,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz",
- "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==",
+ "version": "8.26.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz",
+ "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "8.16.0",
- "@typescript-eslint/types": "8.16.0",
- "@typescript-eslint/typescript-estree": "8.16.0"
+ "@typescript-eslint/scope-manager": "8.26.1",
+ "@typescript-eslint/types": "8.26.1",
+ "@typescript-eslint/typescript-estree": "8.26.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1072,21 +1134,18 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz",
- "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==",
+ "version": "8.26.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz",
+ "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.16.0",
+ "@typescript-eslint/types": "8.26.1",
"eslint-visitor-keys": "^4.2.0"
},
"engines": {
@@ -1123,6 +1182,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -1139,6 +1199,7 @@
"resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
"integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
"dev": true,
+ "license": "MIT",
"peerDependencies": {
"ajv": "^8.5.0"
},
@@ -1153,6 +1214,7 @@
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"ajv": "^8.0.0"
},
@@ -1185,6 +1247,7 @@
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
@@ -1210,6 +1273,7 @@
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
@@ -1222,6 +1286,7 @@
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6"
}
@@ -1316,11 +1381,12 @@
"dev": true
},
"node_modules/esbuild": {
- "version": "0.23.1",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
- "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
+ "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
"dev": true,
"hasInstallScript": true,
+ "license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
@@ -1328,30 +1394,31 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.23.1",
- "@esbuild/android-arm": "0.23.1",
- "@esbuild/android-arm64": "0.23.1",
- "@esbuild/android-x64": "0.23.1",
- "@esbuild/darwin-arm64": "0.23.1",
- "@esbuild/darwin-x64": "0.23.1",
- "@esbuild/freebsd-arm64": "0.23.1",
- "@esbuild/freebsd-x64": "0.23.1",
- "@esbuild/linux-arm": "0.23.1",
- "@esbuild/linux-arm64": "0.23.1",
- "@esbuild/linux-ia32": "0.23.1",
- "@esbuild/linux-loong64": "0.23.1",
- "@esbuild/linux-mips64el": "0.23.1",
- "@esbuild/linux-ppc64": "0.23.1",
- "@esbuild/linux-riscv64": "0.23.1",
- "@esbuild/linux-s390x": "0.23.1",
- "@esbuild/linux-x64": "0.23.1",
- "@esbuild/netbsd-x64": "0.23.1",
- "@esbuild/openbsd-arm64": "0.23.1",
- "@esbuild/openbsd-x64": "0.23.1",
- "@esbuild/sunos-x64": "0.23.1",
- "@esbuild/win32-arm64": "0.23.1",
- "@esbuild/win32-ia32": "0.23.1",
- "@esbuild/win32-x64": "0.23.1"
+ "@esbuild/aix-ppc64": "0.25.1",
+ "@esbuild/android-arm": "0.25.1",
+ "@esbuild/android-arm64": "0.25.1",
+ "@esbuild/android-x64": "0.25.1",
+ "@esbuild/darwin-arm64": "0.25.1",
+ "@esbuild/darwin-x64": "0.25.1",
+ "@esbuild/freebsd-arm64": "0.25.1",
+ "@esbuild/freebsd-x64": "0.25.1",
+ "@esbuild/linux-arm": "0.25.1",
+ "@esbuild/linux-arm64": "0.25.1",
+ "@esbuild/linux-ia32": "0.25.1",
+ "@esbuild/linux-loong64": "0.25.1",
+ "@esbuild/linux-mips64el": "0.25.1",
+ "@esbuild/linux-ppc64": "0.25.1",
+ "@esbuild/linux-riscv64": "0.25.1",
+ "@esbuild/linux-s390x": "0.25.1",
+ "@esbuild/linux-x64": "0.25.1",
+ "@esbuild/netbsd-arm64": "0.25.1",
+ "@esbuild/netbsd-x64": "0.25.1",
+ "@esbuild/openbsd-arm64": "0.25.1",
+ "@esbuild/openbsd-x64": "0.25.1",
+ "@esbuild/sunos-x64": "0.25.1",
+ "@esbuild/win32-arm64": "0.25.1",
+ "@esbuild/win32-ia32": "0.25.1",
+ "@esbuild/win32-x64": "0.25.1"
}
},
"node_modules/escape-string-regexp": {
@@ -1367,29 +1434,31 @@
}
},
"node_modules/eslint": {
- "version": "9.16.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz",
- "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==",
+ "version": "9.22.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz",
+ "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.19.0",
- "@eslint/core": "^0.9.0",
- "@eslint/eslintrc": "^3.2.0",
- "@eslint/js": "9.16.0",
- "@eslint/plugin-kit": "^0.2.3",
+ "@eslint/config-array": "^0.19.2",
+ "@eslint/config-helpers": "^0.1.0",
+ "@eslint/core": "^0.12.0",
+ "@eslint/eslintrc": "^3.3.0",
+ "@eslint/js": "9.22.0",
+ "@eslint/plugin-kit": "^0.2.7",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.1",
+ "@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
- "cross-spawn": "^7.0.5",
+ "cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.2.0",
+ "eslint-scope": "^8.3.0",
"eslint-visitor-keys": "^4.2.0",
"espree": "^10.3.0",
"esquery": "^1.5.0",
@@ -1426,10 +1495,11 @@
}
},
"node_modules/eslint-scope": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
- "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
+ "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
@@ -1521,6 +1591,7 @@
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"estraverse": "^5.2.0"
},
@@ -1553,16 +1624,17 @@
"dev": true
},
"node_modules/fast-glob": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
- "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
- "micromatch": "^4.0.4"
+ "micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
@@ -1573,6 +1645,7 @@
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -1593,10 +1666,11 @@
"dev": true
},
"node_modules/fastq": {
- "version": "1.17.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
- "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
}
@@ -1618,6 +1692,7 @@
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -1661,17 +1736,18 @@
"dev": true
},
"node_modules/fs-extra": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
- "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
+ "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "graceful-fs": "^4.1.2",
- "jsonfile": "^4.0.0",
- "universalify": "^0.1.0"
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
},
"engines": {
- "node": ">=6 <7 || >=8"
+ "node": ">=14.14"
}
},
"node_modules/function-bind": {
@@ -1700,6 +1776,7 @@
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=18"
},
@@ -1711,13 +1788,15 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true
+ "dev": true,
+ "license": "ISC"
},
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/has-flag": {
"version": "4.0.0",
@@ -1750,10 +1829,11 @@
}
},
"node_modules/import-fresh": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
- "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@@ -1770,6 +1850,7 @@
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
"integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8"
}
@@ -1824,6 +1905,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.12.0"
}
@@ -1838,13 +1920,15 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
"integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
@@ -1856,7 +1940,8 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
+ "dev": true,
+ "license": "Python-2.0"
},
"node_modules/json-buffer": {
"version": "3.0.1",
@@ -1868,7 +1953,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@@ -1877,10 +1963,14 @@
"dev": true
},
"node_modules/jsonfile": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
- "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@@ -1939,6 +2029,7 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
@@ -1951,6 +2042,7 @@
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">= 8"
}
@@ -1960,6 +2052,7 @@
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
@@ -2044,6 +2137,7 @@
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
},
@@ -2080,6 +2174,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8.6"
},
@@ -2123,13 +2218,15 @@
"type": "consulting",
"url": "https://feross.org/support"
}
- ]
+ ],
+ "license": "MIT"
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -2156,15 +2253,17 @@
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"dev": true,
+ "license": "MIT",
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
@@ -2189,6 +2288,7 @@
"url": "https://feross.org/support"
}
],
+ "license": "MIT",
"dependencies": {
"queue-microtask": "^1.2.2"
}
@@ -2198,6 +2298,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -2242,13 +2343,15 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
- "dev": true
+ "dev": true,
+ "license": "BSD-3-Clause"
},
"node_modules/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.6.19"
}
@@ -2270,6 +2373,7 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -2297,6 +2401,7 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
@@ -2305,15 +2410,16 @@
}
},
"node_modules/ts-api-utils": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
- "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
+ "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=16"
+ "node": ">=18.12"
},
"peerDependencies": {
- "typescript": ">=4.2.0"
+ "typescript": ">=4.8.4"
}
},
"node_modules/type-check": {
@@ -2329,10 +2435,11 @@
}
},
"node_modules/typescript": {
- "version": "5.5.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
- "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+ "version": "5.8.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
+ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true,
+ "license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -2342,14 +2449,15 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.16.0.tgz",
- "integrity": "sha512-wDkVmlY6O2do4V+lZd0GtRfbtXbeD0q9WygwXXSJnC1xorE8eqyC2L1tJimqpSeFrOzRlYtWnUp/uzgHQOgfBQ==",
+ "version": "8.26.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.26.1.tgz",
+ "integrity": "sha512-t/oIs9mYyrwZGRpDv3g+3K6nZ5uhKEMt2oNmAPwaY4/ye0+EH4nXIPYNtkYFS6QHm+1DFg34DbglYBz5P9Xysg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.16.0",
- "@typescript-eslint/parser": "8.16.0",
- "@typescript-eslint/utils": "8.16.0"
+ "@typescript-eslint/eslint-plugin": "8.26.1",
+ "@typescript-eslint/parser": "8.26.1",
+ "@typescript-eslint/utils": "8.26.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2359,21 +2467,18 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
}
},
"node_modules/universalify": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
- "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">= 4.0.0"
+ "node": ">= 10.0.0"
}
},
"node_modules/uri-js": {
@@ -2413,7 +2518,8 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "dev": true,
+ "license": "ISC"
},
"node_modules/yocto-queue": {
"version": "0.1.0",
diff --git a/package.json b/package.json
index 0ead9c3..a1dfe19 100644
--- a/package.json
+++ b/package.json
@@ -30,12 +30,12 @@
"@types/dom-webcodecs": "^0.1.13"
},
"devDependencies": {
- "@eslint/js": "^9.16.0",
- "@microsoft/api-extractor": "^7.48.0",
- "@stylistic/eslint-plugin": "^2.11.0",
- "esbuild": "^0.23.1",
- "eslint": "^9.16.0",
- "typescript": "^5.5.4",
- "typescript-eslint": "^8.16.0"
+ "@eslint/js": "^9.22.0",
+ "@microsoft/api-extractor": "^7.52.1",
+ "@stylistic/eslint-plugin": "^4.2.0",
+ "esbuild": "^0.25.1",
+ "eslint": "^9.22.0",
+ "typescript": "^5.8.2",
+ "typescript-eslint": "^8.26.1"
}
}
diff --git a/src/conversion.ts b/src/conversion.ts
index df84e7d..75b4410 100644
--- a/src/conversion.ts
+++ b/src/conversion.ts
@@ -13,24 +13,25 @@ import { Input } from './input';
import { InputAudioTrack, InputTrack, InputVideoTrack } from './input-track';
import {
AudioBufferSink,
- AudioDataSink,
+ AudioSampleSink,
CanvasSink,
EncodedPacketSink,
- VideoFrameSink,
+ VideoSampleSink,
} from './media-sink';
import {
AudioBufferSource,
- AudioDataSource,
AudioEncodingConfig,
AudioSource,
EncodedVideoPacketSource,
EncodedAudioPacketSource,
VideoEncodingConfig,
- VideoFrameSource,
VideoSource,
+ VideoSampleSource,
+ AudioSampleSource,
} from './media-source';
-import { assert, clamp, normalizeRotation, promiseWithResolvers, Rotation, setVideoFrameTiming } from './misc';
+import { assert, clamp, normalizeRotation, promiseWithResolvers, Rotation } from './misc';
import { Output, TrackType } from './output';
+import { VideoSample } from './sample';
/** @public */
export type ConversionOptions = {
@@ -467,7 +468,7 @@ export class Conversion {
};
if (needsRerender) {
- const source = new VideoFrameSource(encodingConfig);
+ const source = new VideoSampleSource(encodingConfig);
videoSource = source;
this._trackPromises.push((async () => {
@@ -491,36 +492,37 @@ export class Conversion {
return;
}
- await source.add(new VideoFrame(canvas, {
- timestamp: 1e6 * Math.max(timestamp - this._startTimestamp, 0),
- duration: 1e6 * duration,
- }));
+ const sample = new VideoSample(canvas, {
+ timestamp: Math.max(timestamp - this._startTimestamp, 0),
+ duration,
+ });
+
+ await source.add(sample);
+ sample.close();
}
})());
} else {
- const source = new VideoFrameSource(encodingConfig);
+ const source = new VideoSampleSource(encodingConfig);
videoSource = source;
this._trackPromises.push((async () => {
await this._started;
- const sink = new VideoFrameSink(track);
+ const sink = new VideoSampleSink(track);
- for await (const { frame, timestamp } of sink.frames(this._startTimestamp, this._endTimestamp)) {
- if (this._synchronizer.shouldWait(track.id, timestamp)) {
- await this._synchronizer.wait(timestamp);
+ for await (const sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
+ if (this._synchronizer.shouldWait(track.id, sample.timestamp)) {
+ await this._synchronizer.wait(sample.timestamp);
}
- const clone = setVideoFrameTiming(frame, {
- timestamp: Math.max(timestamp - this._startTimestamp, 0),
- });
+ sample.setTimestamp(Math.max(sample.timestamp - this._startTimestamp, 0));
if (this._canceled) {
return;
}
- await source.add(clone);
- clone.close();
+ await source.add(sample);
+ sample.close();
}
await source.close();
@@ -662,7 +664,7 @@ export class Conversion {
if (needsResample) {
audioSource = this._resampleAudio(track, codecOfChoice, numberOfChannels, sampleRate);
} else {
- const source = new AudioDataSource({
+ const source = new AudioSampleSource({
codec: codecOfChoice,
bitrate: this._options.audio?.bitrate ?? QUALITY_HIGH,
onEncodedPacket: packet => this._reportProgress(track.id, packet.timestamp + packet.duration),
@@ -672,18 +674,18 @@ export class Conversion {
this._trackPromises.push((async () => {
await this._started;
- const sink = new AudioDataSink(track);
- for await (const { data, timestamp } of sink.data(undefined, this._endTimestamp)) {
- if (this._synchronizer.shouldWait(track.id, timestamp)) {
- await this._synchronizer.wait(timestamp);
+ const sink = new AudioSampleSink(track);
+ for await (const sample of sink.samples(undefined, this._endTimestamp)) {
+ if (this._synchronizer.shouldWait(track.id, sample.timestamp)) {
+ await this._synchronizer.wait(sample.timestamp);
}
if (this._canceled) {
return;
}
- await source.add(data);
- data.close();
+ await source.add(sample);
+ sample.close();
}
await source.close();
diff --git a/src/custom-coder.ts b/src/custom-coder.ts
index 9a6c3c8..715825a 100644
--- a/src/custom-coder.ts
+++ b/src/custom-coder.ts
@@ -1,11 +1,12 @@
import { AudioCodec, VideoCodec } from './codec';
import { EncodedPacket } from './packet';
+import { AudioSample, VideoSample } from './sample';
/** @public */
export abstract class CustomVideoDecoder {
codec!: VideoCodec;
config!: VideoDecoderConfig;
- onFrame!: (frame: VideoFrame) => unknown;
+ onSample!: (sample: VideoSample) => unknown;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
static supports(codec: VideoCodec, config: VideoDecoderConfig): boolean {
@@ -22,7 +23,7 @@ export abstract class CustomVideoDecoder {
export abstract class CustomAudioDecoder {
codec!: AudioCodec;
config!: AudioDecoderConfig;
- onData!: (data: AudioData) => unknown;
+ onSample!: (sample: AudioSample) => unknown;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
static supports(codec: AudioCodec, config: AudioDecoderConfig): boolean {
@@ -47,7 +48,7 @@ export abstract class CustomVideoEncoder {
}
abstract init(): void;
- abstract encode(videoFrame: VideoFrame, options: VideoEncoderEncodeOptions): Promise | void;
+ abstract encode(videoSample: VideoSample, options: VideoEncoderEncodeOptions): Promise | void;
abstract flush(): Promise | void;
abstract close(): Promise | void;
}
@@ -64,7 +65,7 @@ export abstract class CustomAudioEncoder {
}
abstract init(): void;
- abstract encode(audioData: AudioData): Promise | void;
+ abstract encode(audioSample: AudioSample): Promise | void;
abstract flush(): Promise | void;
abstract close(): Promise | void;
}
diff --git a/src/index.ts b/src/index.ts
index 24ca56a..71b15ec 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -30,12 +30,12 @@ export {
MediaSource,
VideoSource,
EncodedVideoPacketSource,
- VideoFrameSource,
+ VideoSampleSource,
CanvasSource,
MediaStreamVideoTrackSource,
AudioSource,
EncodedAudioPacketSource,
- AudioDataSource,
+ AudioSampleSource,
AudioBufferSource,
MediaStreamAudioTrackSource,
SubtitleSource,
@@ -67,7 +67,7 @@ export {
getEncodableSubtitleCodecs,
} from './codec';
export { Target, BufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions } from './target';
-export { Rotation, AnyIterable, setVideoFrameTiming } from './misc';
+export { Rotation, AnyIterable, SetRequired } from './misc';
export { Source, BufferSource, StreamSource, StreamSourceOptions, BlobSource, UrlSource } from './source';
export {
InputFormat,
@@ -92,20 +92,20 @@ export { Input, InputOptions } from './input';
export { InputTrack, InputVideoTrack, InputAudioTrack, PacketStats } from './input-track';
export { EncodedPacket, PacketType } from './packet';
export {
+ VideoSample,
+ VideoSampleInit,
AudioSample,
AudioSampleInit,
} from './sample';
export {
PacketRetrievalOptions,
EncodedPacketSink,
- BaseMediaFrameSink,
- VideoFrameSink,
- WrappedVideoFrame,
+ BaseMediaSampleSink,
+ VideoSampleSink,
CanvasSinkOptions,
CanvasSink,
WrappedCanvas,
- AudioDataSink,
- WrappedAudioData,
+ AudioSampleSink,
AudioBufferSink,
WrappedAudioBuffer,
} from './media-sink';
diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts
index 26a6d16..ccd0c0b 100644
--- a/src/isobmff/isobmff-demuxer.ts
+++ b/src/isobmff/isobmff-demuxer.ts
@@ -2200,10 +2200,10 @@ abstract class IsobmffTrackBacking implements InputTrackBacking {
// but can jump right into the correct fragment (or at least nearby).
const lookupEntryIndex = this.internalTrack.fragmentLookupTable
? binarySearchLessOrEqual(
- this.internalTrack.fragmentLookupTable,
- searchTimestamp,
- x => x.timestamp,
- )
+ this.internalTrack.fragmentLookupTable,
+ searchTimestamp,
+ x => x.timestamp,
+ )
: -1;
const lookupEntry = lookupEntryIndex !== -1
? this.internalTrack.fragmentLookupTable![lookupEntryIndex]!
diff --git a/src/media-sink.ts b/src/media-sink.ts
index e880d0a..b4b3bf5 100644
--- a/src/media-sink.ts
+++ b/src/media-sink.ts
@@ -17,6 +17,7 @@ import {
} from './misc';
import { EncodedPacket } from './packet';
import { fromAlaw, fromUlaw } from './pcm';
+import { AudioSample, VideoSample } from './sample';
/** @public */
export type PacketRetrievalOptions = {
@@ -180,18 +181,11 @@ export class EncodedPacketSink {
}
}
-export type WrappedMediaFrame = {
- frame: T;
- timestamp: number;
- duration: number;
-};
-
abstract class DecoderWrapper<
- MediaFrame extends VideoFrame | AudioData,
- WrappedFrame extends WrappedMediaFrame = WrappedMediaFrame,
+ MediaSample extends VideoSample | AudioSample,
> {
constructor(
- public onFrame: (frame: WrappedFrame) => unknown,
+ public onSample: (sample: MediaSample) => unknown,
public onError: (error: DOMException) => unknown,
) {}
@@ -202,36 +196,29 @@ abstract class DecoderWrapper<
}
/** @public */
-export abstract class BaseMediaFrameSink<
- MediaFrame extends VideoFrame | AudioData,
- /** @internal */
- WrappedFrame extends WrappedMediaFrame = WrappedMediaFrame,
+export abstract class BaseMediaSampleSink<
+ MediaSample extends VideoSample | AudioSample,
> {
/** @internal */
abstract _createDecoder(
- onFrame: (frame: WrappedFrame) => unknown,
+ onSample: (sample: MediaSample) => unknown,
onError: (error: DOMException) => unknown
- ): Promise>;
+ ): Promise>;
/** @internal */
abstract _createPacketSink(): EncodedPacketSink;
/** @internal */
- private _duplicateFrame(frame: WrappedFrame) {
- return structuredClone(frame);
- }
-
- /** @internal */
- protected mediaFramesInRange(
+ protected mediaSamplesInRange(
startTimestamp = 0,
endTimestamp = Infinity,
- ): AsyncGenerator {
+ ): AsyncGenerator {
validateTimestamp(startTimestamp);
validateTimestamp(endTimestamp);
const MAX_QUEUE_SIZE = 8;
- const frameQueue: WrappedFrame[] = [];
- let firstFrameQueued = false;
- let lastFrame: WrappedFrame | null = null;
+ const sampleQueue: MediaSample[] = [];
+ let firstSampleQueued = false;
+ let lastSample: MediaSample | null = null;
let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers();
let decoderIsFlushed = false;
@@ -246,38 +233,38 @@ export abstract class BaseMediaFrameSink<
// The following is the "pump" process that keeps pumping packets into the decoder
(async () => {
const decoderError = new Error();
- const decoder = await this._createDecoder((wrappedFrame) => {
+ const decoder = await this._createDecoder((sample) => {
onQueueDequeue();
- if (wrappedFrame.timestamp >= endTimestamp) {
+ if (sample.timestamp >= endTimestamp) {
ended = true;
}
if (ended) {
- wrappedFrame.frame.close();
+ sample.close();
return;
}
- if (lastFrame) {
- if (wrappedFrame.timestamp > startTimestamp) {
+ if (lastSample) {
+ if (sample.timestamp > startTimestamp) {
// We don't know ahead of time what the first first is. This is because the first first is the
// last first whose timestamp is less than or equal to the start timestamp. Therefore we need to
// wait for the first first after the start timestamp, and then we'll know that the previous
// first was the first first.
- frameQueue.push(lastFrame);
- firstFrameQueued = true;
+ sampleQueue.push(lastSample);
+ firstSampleQueued = true;
} else {
- lastFrame.frame.close();
+ lastSample.close();
}
}
- if (wrappedFrame.timestamp >= startTimestamp) {
- frameQueue.push(wrappedFrame);
- firstFrameQueued = true;
+ if (sample.timestamp >= startTimestamp) {
+ sampleQueue.push(sample);
+ firstSampleQueued = true;
}
- lastFrame = firstFrameQueued ? null : wrappedFrame;
+ lastSample = firstSampleQueued ? null : sample;
- if (frameQueue.length > 0) {
+ if (sampleQueue.length > 0) {
onQueueNotEmpty();
({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
}
@@ -319,7 +306,7 @@ export abstract class BaseMediaFrameSink<
await packets.next(); // Skip the start packet as we already have it
while (currentPacket && !ended) {
- if (frameQueue.length + decoder.getDecodeQueueSize() > MAX_QUEUE_SIZE) {
+ if (sampleQueue.length + decoder.getDecodeQueueSize() > MAX_QUEUE_SIZE) {
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
await queueDequeue;
continue;
@@ -340,8 +327,8 @@ export abstract class BaseMediaFrameSink<
if (!terminated) await decoder.flush();
decoder.close();
- if (!firstFrameQueued && lastFrame) {
- frameQueue.push(lastFrame);
+ if (!firstSampleQueued && lastSample) {
+ sampleQueue.push(lastSample);
}
decoderIsFlushed = true;
@@ -360,8 +347,8 @@ export abstract class BaseMediaFrameSink<
return { value: undefined, done: true };
} else if (outOfBandError) {
throw outOfBandError;
- } else if (frameQueue.length > 0) {
- const value = frameQueue.shift()!;
+ } else if (sampleQueue.length > 0) {
+ const value = sampleQueue.shift()!;
onQueueDequeue();
return { value, done: false };
} else if (!decoderIsFlushed) {
@@ -377,10 +364,10 @@ export abstract class BaseMediaFrameSink<
onQueueDequeue();
onQueueNotEmpty();
- lastFrame?.frame.close();
+ lastSample?.close();
- for (const frame of frameQueue) {
- frame.frame.close();
+ for (const sample of sampleQueue) {
+ sample.close();
}
return { value: undefined, done: true };
@@ -395,15 +382,15 @@ export abstract class BaseMediaFrameSink<
}
/** @internal */
- protected mediaFramesAtTimestamps(
+ protected mediaSamplesAtTimestamps(
timestamps: AnyIterable,
- ): AsyncGenerator {
+ ): AsyncGenerator {
validateAnyIterable(timestamps);
const timestampIterator = toAsyncIterator(timestamps);
const timestampsOfInterest: number[] = [];
const MAX_QUEUE_SIZE = 8;
- const frameQueue: (WrappedFrame | null)[] = [];
+ const sampleQueue: (MediaSample | null)[] = [];
let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers();
let decoderIsFlushed = false;
@@ -414,9 +401,9 @@ export abstract class BaseMediaFrameSink<
// the consumer.
let outOfBandError = null as Error | null;
- let lastUsedFrame = null as WrappedFrame | null;
- const pushToQueue = (frame: WrappedFrame | null) => {
- frameQueue.push(frame);
+ let lastUsedSample = null as MediaSample | null;
+ const pushToQueue = (sample: MediaSample | null) => {
+ sampleQueue.push(sample);
onQueueNotEmpty();
({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
};
@@ -424,29 +411,29 @@ export abstract class BaseMediaFrameSink<
// The following is the "pump" process that keeps pumping packets into the decoder
(async () => {
const decoderError = new Error();
- const decoder = await this._createDecoder((wrappedFrame) => {
+ const decoder = await this._createDecoder((sample) => {
onQueueDequeue();
if (terminated) {
- wrappedFrame.frame.close();
+ sample.close();
return;
}
- let frameUsed = false;
+ let sampleUsed = false;
while (
timestampsOfInterest.length > 0
- && wrappedFrame.timestamp - timestampsOfInterest[0]! > -1e-10 // Give it a little epsilon
+ && sample.timestamp - timestampsOfInterest[0]! > -1e-10 // Give it a little epsilon
) {
- pushToQueue(this._duplicateFrame(wrappedFrame));
+ pushToQueue(sample.clone() as MediaSample);
timestampsOfInterest.shift();
- frameUsed = true;
+ sampleUsed = true;
}
- if (frameUsed) {
- lastUsedFrame?.frame.close();
- lastUsedFrame = wrappedFrame;
+ if (sampleUsed) {
+ lastUsedSample?.close();
+ lastUsedSample = sample;
} else {
- wrappedFrame.frame.close();
+ sample.close();
}
}, (error) => {
if (!outOfBandError) {
@@ -463,7 +450,7 @@ export abstract class BaseMediaFrameSink<
for await (const timestamp of timestampIterator) {
validateTimestamp(timestamp);
- while (frameQueue.length + decoder.getDecodeQueueSize() > MAX_QUEUE_SIZE && !terminated) {
+ while (sampleQueue.length + decoder.getDecodeQueueSize() > MAX_QUEUE_SIZE && !terminated) {
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
await queueDequeue;
}
@@ -501,10 +488,10 @@ export abstract class BaseMediaFrameSink<
targetPacket.sequenceNumber === lastPacket.sequenceNumber
&& timestampsOfInterest.length === 0
) {
- // Special case: We have a repeat packet, but the frame for that packet has already been
- // decoded. Therefore, we need to push the frame here instead of in the decoder callback.
- if (lastUsedFrame) {
- pushToQueue(this._duplicateFrame(lastUsedFrame));
+ // Special case: We have a repeat packet, but the sample for that packet has already been
+ // decoded. Therefore, we need to push the sample here instead of in the decoder callback.
+ if (lastUsedSample) {
+ pushToQueue(lastUsedSample.clone() as MediaSample);
}
} else {
timestampsOfInterest.push(targetPacket.timestamp);
@@ -528,7 +515,7 @@ export abstract class BaseMediaFrameSink<
if (!terminated) {
await decoder.flush();
- lastUsedFrame?.frame.close();
+ lastUsedSample?.close();
}
decoder.close();
@@ -548,8 +535,8 @@ export abstract class BaseMediaFrameSink<
return { value: undefined, done: true };
} else if (outOfBandError) {
throw outOfBandError;
- } else if (frameQueue.length > 0) {
- const value = frameQueue.shift();
+ } else if (sampleQueue.length > 0) {
+ const value = sampleQueue.shift();
assert(value !== undefined);
onQueueDequeue();
return { value, done: false };
@@ -565,10 +552,10 @@ export abstract class BaseMediaFrameSink<
onQueueDequeue();
onQueueNotEmpty();
- for (const frame of frameQueue) {
- frame?.frame.close();
+ for (const sample of sampleQueue) {
+ sample?.close();
}
- lastUsedFrame?.frame.close();
+ lastUsedSample?.close();
return { value: undefined, done: true };
},
@@ -582,44 +569,45 @@ export abstract class BaseMediaFrameSink<
}
}
-class VideoDecoderWrapper extends DecoderWrapper {
+class VideoDecoderWrapper extends DecoderWrapper {
decoder: VideoDecoder | null = null;
customDecoder: CustomVideoDecoder | null = null;
lastCustomDecoderPromise = Promise.resolve();
customDecoderQueueSize = 0;
- frameQueue: VideoFrame[] = [];
+ sampleQueue: VideoSample[] = [];
constructor(
- onFrame: (frame: WrappedMediaFrame) => unknown,
+ onSample: (sample: VideoSample) => unknown,
onError: (error: DOMException) => unknown,
codec: VideoCodec,
decoderConfig: VideoDecoderConfig,
+ public rotation: Rotation,
public timeResolution: number,
) {
- super(onFrame, onError);
+ super(onSample, onError);
- const frameHandler = (frame: VideoFrame) => {
+ const sampleHandler = (sample: VideoSample) => {
// For correct B-frame handling, we don't just hand over the frames directly but instead add them to a
// queue, because we want to ensure frames are emitted in presentation order. We flush the queue each time
// we receive a frame with a timestamp larger than the highest we've seen so far, as we can sure that is
// not a B-frame. Typically, WebCodecs automatically guarantees that frames are emitted in presentation
// order, but some browsers (Safari) don't always follow this rule.
- if (this.frameQueue.length > 0 && (frame.timestamp >= last(this.frameQueue)!.timestamp)) {
- for (const frame of this.frameQueue) {
- this.wrapAndEmitFrame(frame);
+ if (this.sampleQueue.length > 0 && (sample.timestamp >= last(this.sampleQueue)!.timestamp)) {
+ for (const sample of this.sampleQueue) {
+ this.finalizeAndEmitSample(sample);
}
- this.frameQueue.length = 0;
+ this.sampleQueue.length = 0;
}
const insertionIndex = binarySearchLessOrEqual(
- this.frameQueue,
- frame.timestamp,
+ this.sampleQueue,
+ sample.timestamp,
x => x.timestamp,
);
- this.frameQueue.splice(insertionIndex + 1, 0, frame);
+ this.sampleQueue.splice(insertionIndex + 1, 0, sample);
};
const MatchingCustomDecoder = customVideoDecoders.find(x => x.supports(codec, decoderConfig));
@@ -628,28 +616,24 @@ class VideoDecoderWrapper extends DecoderWrapper {
this.customDecoder = new MatchingCustomDecoder() as CustomVideoDecoder;
this.customDecoder.codec = codec;
this.customDecoder.config = decoderConfig;
- this.customDecoder.onFrame = frameHandler;
+ this.customDecoder.onSample = sampleHandler;
this.customDecoder.init();
} else {
this.decoder = new VideoDecoder({
- output: frameHandler,
+ output: frame => sampleHandler(new VideoSample(frame)),
error: onError,
});
this.decoder.configure(decoderConfig);
}
}
- wrapAndEmitFrame(frame: VideoFrame) {
- // Round the microsecond timestamps to the time resolution
- const timestamp = Math.round(frame.timestamp / 1e6 * this.timeResolution) / this.timeResolution;
- const duration = Math.round((frame.duration ?? 0) / 1e6 * this.timeResolution) / this.timeResolution;
+ finalizeAndEmitSample(sample: VideoSample) {
+ // Round the timestamps to the time resolution
+ sample.setTimestamp(Math.round(sample.timestamp * this.timeResolution) / this.timeResolution);
+ sample.setDuration(Math.round(sample.duration * this.timeResolution) / this.timeResolution);
- this.onFrame({
- frame,
- timestamp,
- duration,
- });
+ this.onSample(sample);
}
getDecodeQueueSize() {
@@ -683,10 +667,10 @@ class VideoDecoderWrapper extends DecoderWrapper {
await this.decoder.flush();
}
- for (const frame of this.frameQueue) {
- this.wrapAndEmitFrame(frame);
+ for (const sample of this.sampleQueue) {
+ this.finalizeAndEmitSample(sample);
}
- this.frameQueue.length = 0;
+ this.sampleQueue.length = 0;
}
close() {
@@ -697,22 +681,15 @@ class VideoDecoderWrapper extends DecoderWrapper {
this.decoder.close();
}
- for (const frame of this.frameQueue) {
- frame.close();
+ for (const sample of this.sampleQueue) {
+ sample.close();
}
- this.frameQueue.length = 0;
+ this.sampleQueue.length = 0;
}
}
/** @public */
-export type WrappedVideoFrame = {
- frame: VideoFrame;
- timestamp: number;
- duration: number;
-};
-
-/** @public */
-export class VideoFrameSink extends BaseMediaFrameSink {
+export class VideoSampleSink extends BaseMediaSampleSink {
/** @internal */
_videoTrack: InputVideoTrack;
@@ -728,7 +705,7 @@ export class VideoFrameSink extends BaseMediaFrameSink {
/** @internal */
async _createDecoder(
- onFrame: (frame: WrappedMediaFrame) => unknown,
+ onSample: (sample: VideoSample) => unknown,
onError: (error: DOMException) => unknown,
) {
if (!(await this._videoTrack.canDecode())) {
@@ -739,11 +716,12 @@ export class VideoFrameSink extends BaseMediaFrameSink {
}
const codec = this._videoTrack.codec;
+ const rotation = this._videoTrack.rotation;
const decoderConfig = await this._videoTrack.getDecoderConfig();
const timeResolution = this._videoTrack.timeResolution;
assert(codec && decoderConfig);
- return new VideoDecoderWrapper(onFrame, onError, codec, decoderConfig, timeResolution);
+ return new VideoDecoderWrapper(onSample, onError, codec, decoderConfig, rotation, timeResolution);
}
/** @internal */
@@ -751,36 +729,21 @@ export class VideoFrameSink extends BaseMediaFrameSink {
return new EncodedPacketSink(this._videoTrack);
}
- /** @internal */
- _wrappedFrameToWrappedVideoFrame(frame: WrappedMediaFrame): WrappedVideoFrame {
- return {
- frame: frame.frame,
- timestamp: frame.timestamp,
- duration: frame.duration,
- };
- }
-
- async getFrame(timestamp: number) {
+ async getSample(timestamp: number) {
validateTimestamp(timestamp);
- for await (const frame of this.mediaFramesAtTimestamps([timestamp])) {
- return frame && this._wrappedFrameToWrappedVideoFrame(frame);
+ for await (const sample of this.mediaSamplesAtTimestamps([timestamp])) {
+ return sample;
}
throw new Error('Internal error: Iterator returned nothing.');
}
- frames(startTimestamp = 0, endTimestamp = Infinity) {
- return mapAsyncGenerator(
- this.mediaFramesInRange(startTimestamp, endTimestamp),
- frame => this._wrappedFrameToWrappedVideoFrame(frame),
- );
+ samples(startTimestamp = 0, endTimestamp = Infinity) {
+ return this.mediaSamplesInRange(startTimestamp, endTimestamp);
}
- framesAtTimestamps(timestamps: AnyIterable) {
- return mapAsyncGenerator(
- this.mediaFramesAtTimestamps(timestamps),
- frame => frame && this._wrappedFrameToWrappedVideoFrame(frame),
- );
+ samplesAtTimestamps(timestamps: AnyIterable) {
+ return this.mediaSamplesAtTimestamps(timestamps);
}
}
@@ -813,7 +776,7 @@ export class CanvasSink {
/** @internal */
_rotation: Rotation;
/** @internal */
- _videoFrameSink: VideoFrameSink;
+ _videoSampleSink: VideoSampleSink;
/** @internal */
_canvasPool: (HTMLCanvasElement | OffscreenCanvas | null)[];
/** @internal */
@@ -877,12 +840,12 @@ export class CanvasSink {
this._height = height;
this._rotation = rotation;
this._fit = options.fit ?? 'fill';
- this._videoFrameSink = new VideoFrameSink(videoTrack);
+ this._videoSampleSink = new VideoSampleSink(videoTrack);
this._canvasPool = Array.from({ length: options.poolSize ?? 0 }, () => null);
}
/** @internal */
- _videoFrameToWrappedCanvas(frame: WrappedVideoFrame): WrappedCanvas {
+ _videoSampleToWrappedCanvas(sample: VideoSample): WrappedCanvas {
let canvas = this._canvasPool[this._nextCanvasIndex];
if (!canvas) {
if (typeof OffscreenCanvas !== 'undefined') {
@@ -903,12 +866,13 @@ export class CanvasSink {
this._nextCanvasIndex = (this._nextCanvasIndex + 1) % this._canvasPool.length;
}
- const context = canvas.getContext('2d', { alpha: false });
+ const context
+ = canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
assert(context);
context.resetTransform();
- // These variables specify where the final frame will be drawn on the canvas
+ // These variables specify where the final sample will be drawn on the canvas
let dx: number;
let dy: number;
let newWidth: number;
@@ -920,15 +884,15 @@ export class CanvasSink {
newWidth = this._width;
newHeight = this._height;
} else {
- const [frameWidth, frameHeight] = this._rotation % 180 === 0
- ? [frame.frame.codedWidth, frame.frame.codedHeight]
- : [frame.frame.codedHeight, frame.frame.codedWidth];
+ const [sampleWidth, sampleHeight] = this._rotation % 180 === 0
+ ? [sample.codedWidth, sample.codedHeight]
+ : [sample.codedHeight, sample.codedWidth];
const scale = this._fit === 'contain'
- ? Math.min(this._width / frameWidth, this._height / frameHeight)
- : Math.max(this._width / frameWidth, this._height / frameHeight);
- newWidth = frameWidth * scale;
- newHeight = frameHeight * scale;
+ ? Math.min(this._width / sampleWidth, this._height / sampleHeight)
+ : Math.max(this._width / sampleWidth, this._height / sampleHeight);
+ newWidth = sampleWidth * scale;
+ newHeight = sampleHeight * scale;
dx = (this._width - newWidth) / 2;
dy = (this._height - newHeight) / 2;
}
@@ -936,46 +900,46 @@ export class CanvasSink {
const aspectRatioChange = this._rotation % 180 === 0 ? 1 : newWidth / newHeight;
context.translate(this._width / 2, this._height / 2);
context.rotate(this._rotation * Math.PI / 180);
- // This aspect ratio compensation is done so that we can draw the frame with the intended dimensions and
+ // This aspect ratio compensation is done so that we can draw the sample with the intended dimensions and
// don't need to think about how those dimensions change after the rotation
context.scale(1 / aspectRatioChange, aspectRatioChange);
context.translate(-this._width / 2, -this._height / 2);
- context.drawImage(frame.frame, dx, dy, newWidth, newHeight);
+ context.drawImage(sample.toCanvasImageSource(), dx, dy, newWidth, newHeight);
const result = {
canvas,
- timestamp: frame.timestamp,
- duration: frame.duration,
+ timestamp: sample.timestamp,
+ duration: sample.duration,
};
- frame.frame.close();
+ sample.close();
return result;
}
async getCanvas(timestamp: number) {
validateTimestamp(timestamp);
- const frame = await this._videoFrameSink.getFrame(timestamp);
- return frame && this._videoFrameToWrappedCanvas(frame);
+ const sample = await this._videoSampleSink.getSample(timestamp);
+ return sample && this._videoSampleToWrappedCanvas(sample);
}
canvases(startTimestamp = 0, endTimestamp = Infinity) {
return mapAsyncGenerator(
- this._videoFrameSink.frames(startTimestamp, endTimestamp),
- frame => this._videoFrameToWrappedCanvas(frame),
+ this._videoSampleSink.samples(startTimestamp, endTimestamp),
+ sample => this._videoSampleToWrappedCanvas(sample),
);
}
canvasesAtTimestamps(timestamps: AnyIterable) {
return mapAsyncGenerator(
- this._videoFrameSink.framesAtTimestamps(timestamps),
- frame => frame && this._videoFrameToWrappedCanvas(frame),
+ this._videoSampleSink.samplesAtTimestamps(timestamps),
+ sample => sample && this._videoSampleToWrappedCanvas(sample),
);
}
}
-class AudioDecoderWrapper extends DecoderWrapper {
+class AudioDecoderWrapper extends DecoderWrapper {
decoder: AudioDecoder | null = null;
customDecoder: CustomAudioDecoder | null = null;
@@ -983,25 +947,20 @@ class AudioDecoderWrapper extends DecoderWrapper {
customDecoderQueueSize = 0;
constructor(
- onData: (data: WrappedMediaFrame) => unknown,
+ onSample: (sample: AudioSample) => unknown,
onError: (error: DOMException) => unknown,
codec: AudioCodec,
decoderConfig: AudioDecoderConfig,
) {
- super(onData, onError);
+ super(onSample, onError);
- const dataHandler = (data: AudioData) => {
+ const sampleHandler = (sample: AudioSample) => {
const sampleRate = decoderConfig.sampleRate;
- // Round the microsecond timestamps to the sample rate
- const timestamp = Math.round(data.timestamp / 1e6 * sampleRate) / sampleRate;
- const duration = Math.round(data.duration / 1e6 * sampleRate) / sampleRate;
+ // Round the timestamp to the sample rate
+ sample.setTimestamp(Math.round(sample.timestamp * sampleRate) / sampleRate);
- onData({
- frame: data,
- timestamp,
- duration,
- });
+ onSample(sample);
};
const MatchingCustomDecoder = customAudioDecoders.find(x => x.supports(codec, decoderConfig));
@@ -1010,12 +969,12 @@ class AudioDecoderWrapper extends DecoderWrapper {
this.customDecoder = new MatchingCustomDecoder() as CustomAudioDecoder;
this.customDecoder.codec = codec;
this.customDecoder.config = decoderConfig;
- this.customDecoder.onData = dataHandler;
+ this.customDecoder.onSample = sampleHandler;
this.customDecoder.init();
} else {
this.decoder = new AudioDecoder({
- output: dataHandler,
+ output: data => sampleHandler(new AudioSample(data)),
error: onError,
});
this.decoder.configure(decoderConfig);
@@ -1066,7 +1025,7 @@ class AudioDecoderWrapper extends DecoderWrapper {
// There are a lot of PCM variants not natively supported by the browser and by AudioData. Therefore we need a simple
// decoder that maps any input PCM format into a PCM format supported by the browser.
-class PcmAudioDecoderWrapper extends DecoderWrapper {
+class PcmAudioDecoderWrapper extends DecoderWrapper {
codec: PcmAudioCodec;
inputSampleSize: 1 | 2 | 3 | 4;
@@ -1081,11 +1040,11 @@ class PcmAudioDecoderWrapper extends DecoderWrapper {
currentTimestamp: number | null = null;
constructor(
- onData: (data: WrappedMediaFrame) => unknown,
+ onSample: (sample: AudioSample) => unknown,
onError: (error: DOMException) => unknown,
public decoderConfig: AudioDecoderConfig,
) {
- super(onData, onError);
+ super(onSample, onError);
assert((PCM_AUDIO_CODECS as readonly string[]).includes(decoderConfig.codec));
this.codec = decoderConfig.codec as PcmAudioCodec;
@@ -1207,21 +1166,16 @@ class PcmAudioDecoderWrapper extends DecoderWrapper {
const preciseTimestamp = this.currentTimestamp;
this.currentTimestamp += preciseDuration;
- const audioData = new AudioData({
+ const audioSample = new AudioSample({
format: this.outputFormat,
data: outputBuffer,
numberOfChannels: this.decoderConfig.numberOfChannels,
sampleRate: this.decoderConfig.sampleRate,
numberOfFrames,
- timestamp: 1e6 * preciseTimestamp,
+ timestamp: preciseTimestamp,
});
- // Since all other decoders are async, we'll make this one behave async as well
- queueMicrotask(() => this.onFrame({
- frame: audioData,
- timestamp: preciseTimestamp,
- duration: preciseDuration,
- }));
+ this.onSample(audioSample);
}
async flush() {
@@ -1234,14 +1188,7 @@ class PcmAudioDecoderWrapper extends DecoderWrapper {
}
/** @public */
-export type WrappedAudioData = {
- data: AudioData;
- timestamp: number;
- duration: number;
-};
-
-/** @public */
-export class AudioDataSink extends BaseMediaFrameSink {
+export class AudioSampleSink extends BaseMediaSampleSink {
/** @internal */
_audioTrack: InputAudioTrack;
@@ -1257,7 +1204,7 @@ export class AudioDataSink extends BaseMediaFrameSink {
/** @internal */
async _createDecoder(
- onData: (data: WrappedMediaFrame) => unknown,
+ onSample: (sample: AudioSample) => unknown,
onError: (error: DOMException) => unknown,
) {
if (!(await this._audioTrack.canDecode())) {
@@ -1272,47 +1219,32 @@ export class AudioDataSink extends BaseMediaFrameSink {
assert(codec && decoderConfig);
if ((PCM_AUDIO_CODECS as readonly string[]).includes(decoderConfig.codec)) {
- return new PcmAudioDecoderWrapper(onData, onError, decoderConfig);
+ return new PcmAudioDecoderWrapper(onSample, onError, decoderConfig);
} else {
- return new AudioDecoderWrapper(onData, onError, codec, decoderConfig);
+ return new AudioDecoderWrapper(onSample, onError, codec, decoderConfig);
}
}
- /** @internal */
- _wrappedFrameToWrappedAudioData(frame: WrappedMediaFrame): WrappedAudioData {
- return {
- data: frame.frame,
- timestamp: frame.timestamp,
- duration: frame.duration,
- };
- }
-
/** @internal */
_createPacketSink() {
return new EncodedPacketSink(this._audioTrack);
}
- async getData(timestamp: number) {
+ async getSample(timestamp: number) {
validateTimestamp(timestamp);
- for await (const data of this.mediaFramesAtTimestamps([timestamp])) {
- return data && this._wrappedFrameToWrappedAudioData(data);
+ for await (const sample of this.mediaSamplesAtTimestamps([timestamp])) {
+ return sample;
}
throw new Error('Internal error: Iterator returned nothing.');
}
- data(startTimestamp = 0, endTimestamp = Infinity) {
- return mapAsyncGenerator(
- this.mediaFramesInRange(startTimestamp, endTimestamp),
- data => this._wrappedFrameToWrappedAudioData(data),
- );
+ samples(startTimestamp = 0, endTimestamp = Infinity) {
+ return this.mediaSamplesInRange(startTimestamp, endTimestamp);
}
- dataAtTimestamps(timestamps: AnyIterable) {
- return mapAsyncGenerator(
- this.mediaFramesAtTimestamps(timestamps),
- data => data && this._wrappedFrameToWrappedAudioData(data),
- );
+ samplesAtTimestamps(timestamps: AnyIterable) {
+ return this.mediaSamplesAtTimestamps(timestamps);
}
}
@@ -1326,60 +1258,43 @@ export type WrappedAudioBuffer = {
/** @public */
export class AudioBufferSink {
/** @internal */
- _audioDataSink: AudioDataSink;
+ _audioSampleSink: AudioSampleSink;
constructor(audioTrack: InputAudioTrack) {
if (!(audioTrack instanceof InputAudioTrack)) {
throw new TypeError('audioTrack must be an InputAudioTrack.');
}
- this._audioDataSink = new AudioDataSink(audioTrack);
+ this._audioSampleSink = new AudioSampleSink(audioTrack);
}
/** @internal */
- _audioDataToWrappedArrayBuffer(data: WrappedAudioData): WrappedAudioBuffer {
- const audioBuffer = new AudioBuffer({
- numberOfChannels: data.data.numberOfChannels,
- length: data.data.numberOfFrames,
- sampleRate: data.data.sampleRate,
- });
-
- // All user agents are required to support conversion to f32-planar
- const dataBytes = new Float32Array(data.data.allocationSize({ planeIndex: 0, format: 'f32-planar' }) / 4);
-
- for (let i = 0; i < data.data.numberOfChannels; i++) {
- data.data.copyTo(dataBytes, { planeIndex: i, format: 'f32-planar' });
- audioBuffer.copyToChannel(dataBytes, i);
- }
-
- const result = {
- buffer: audioBuffer,
- timestamp: data.timestamp,
- duration: data.duration,
+ _audioSampleToWrappedArrayBuffer(sample: AudioSample): WrappedAudioBuffer {
+ return {
+ buffer: sample.toAudioBuffer(),
+ timestamp: sample.timestamp,
+ duration: sample.duration,
};
-
- data.data.close();
- return result;
}
async getBuffer(timestamp: number) {
validateTimestamp(timestamp);
- const data = await this._audioDataSink.getData(timestamp);
- return data && this._audioDataToWrappedArrayBuffer(data);
+ const data = await this._audioSampleSink.getSample(timestamp);
+ return data && this._audioSampleToWrappedArrayBuffer(data);
}
buffers(startTimestamp = 0, endTimestamp = Infinity) {
return mapAsyncGenerator(
- this._audioDataSink.data(startTimestamp, endTimestamp),
- data => this._audioDataToWrappedArrayBuffer(data),
+ this._audioSampleSink.samples(startTimestamp, endTimestamp),
+ data => this._audioSampleToWrappedArrayBuffer(data),
);
}
buffersAtTimestamps(timestamps: AnyIterable) {
return mapAsyncGenerator(
- this._audioDataSink.dataAtTimestamps(timestamps),
- data => data && this._audioDataToWrappedArrayBuffer(data),
+ this._audioSampleSink.samplesAtTimestamps(timestamps),
+ data => data && this._audioSampleToWrappedArrayBuffer(data),
);
}
}
diff --git a/src/media-source.ts b/src/media-source.ts
index e47e055..ecd7470 100644
--- a/src/media-source.ts
+++ b/src/media-source.ts
@@ -26,6 +26,7 @@ import {
customAudioEncoders,
} from './custom-coder';
import { EncodedPacket } from './packet';
+import { AudioSample, VideoSample } from './sample';
/** @public */
export abstract class MediaSource {
@@ -194,33 +195,33 @@ class VideoEncoderWrapper {
constructor(private source: VideoSource, private encodingConfig: VideoEncodingConfig) {}
- async add(videoFrame: VideoFrame, shouldClose: boolean, encodeOptions?: VideoEncoderEncodeOptions) {
+ async add(videoSample: VideoSample, shouldClose: boolean, encodeOptions?: VideoEncoderEncodeOptions) {
this.source._ensureValidAdd();
- // Ensure video frame size remains constant
+ // Ensure video sample size remains constant
if (this.lastWidth !== null && this.lastHeight !== null) {
- if (videoFrame.codedWidth !== this.lastWidth || videoFrame.codedHeight !== this.lastHeight) {
+ if (videoSample.codedWidth !== this.lastWidth || videoSample.codedHeight !== this.lastHeight) {
throw new Error(
- `Video frame size must remain constant. Expected ${this.lastWidth}x${this.lastHeight},`
- + ` got ${videoFrame.codedWidth}x${videoFrame.codedHeight}.`,
+ `Video sample size must remain constant. Expected ${this.lastWidth}x${this.lastHeight},`
+ + ` got ${videoSample.codedWidth}x${videoSample.codedHeight}.`,
);
}
} else {
- this.lastWidth = videoFrame.codedWidth;
- this.lastHeight = videoFrame.codedHeight;
+ this.lastWidth = videoSample.codedWidth;
+ this.lastHeight = videoSample.codedHeight;
}
if (!this.encoderInitialized) {
if (this.ensureEncoderPromise) {
await this.ensureEncoderPromise;
} else {
- await this.ensureEncoder(videoFrame);
+ await this.ensureEncoder(videoSample);
}
}
assert(this.encoderInitialized);
const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 5;
- const multipleOfKeyFrameInterval = Math.floor((videoFrame.timestamp / 1e6) / keyFrameInterval);
+ const multipleOfKeyFrameInterval = Math.floor(videoSample.timestamp / keyFrameInterval);
// Ensure a key frame every KEY_FRAME_INTERVAL seconds. It is important that all video tracks follow the same
// "key frame" rhythm, because aligned key frames are required to start new fragments in ISOBMFF or clusters
@@ -236,14 +237,14 @@ class VideoEncoderWrapper {
if (this.customEncoder) {
this.customEncoderQueueSize++;
this.lastCustomEncoderPromise = this.lastCustomEncoderPromise.then(() => {
- return this.customEncoder!.encode(videoFrame, finalEncodeOptions);
+ return this.customEncoder!.encode(videoSample, finalEncodeOptions);
});
void this.lastCustomEncoderPromise.then(() => {
this.customEncoderQueueSize--;
if (shouldClose) {
- videoFrame.close();
+ videoSample.close();
}
});
@@ -252,10 +253,12 @@ class VideoEncoderWrapper {
}
} else {
assert(this.encoder);
+ const videoFrame = videoSample.toVideoFrame();
this.encoder.encode(videoFrame, finalEncodeOptions);
+ videoFrame.close();
if (shouldClose) {
- videoFrame.close();
+ videoSample.close();
}
// We need to do this after sending the frame to the encoder as the frame otherwise might be closed
@@ -267,7 +270,7 @@ class VideoEncoderWrapper {
await this.muxer!.mutex.currentPromise; // Allow the writer to apply backpressure
}
- private async ensureEncoder(videoFrame: VideoFrame) {
+ private async ensureEncoder(videoSample: VideoSample) {
if (this.encoder) {
return;
}
@@ -275,8 +278,8 @@ class VideoEncoderWrapper {
const { promise, resolve } = promiseWithResolvers();
this.ensureEncoderPromise = promise;
- const width = videoFrame.codedWidth;
- const height = videoFrame.codedHeight;
+ const width = videoSample.codedWidth;
+ const height = videoSample.codedHeight;
const bitrate = this.encodingConfig.bitrate instanceof Quality
? this.encodingConfig.bitrate._toVideoBitrate(this.encodingConfig.codec, width, height)
: this.encodingConfig.bitrate;
@@ -365,7 +368,7 @@ class VideoEncoderWrapper {
}
/** @public */
-export class VideoFrameSource extends VideoSource {
+export class VideoSampleSource extends VideoSource {
/** @internal */
private _encoder: VideoEncoderWrapper;
@@ -376,12 +379,12 @@ export class VideoFrameSource extends VideoSource {
this._encoder = new VideoEncoderWrapper(this, encodingConfig);
}
- add(videoFrame: VideoFrame, encodeOptions?: VideoEncoderEncodeOptions) {
- if (!(videoFrame instanceof VideoFrame)) {
- throw new TypeError('videoFrame must be a VideoFrame.');
+ add(videoSample: VideoSample, encodeOptions?: VideoEncoderEncodeOptions) {
+ if (!(videoSample instanceof VideoSample)) {
+ throw new TypeError('videoSample must be a VideoSample.');
}
- return this._encoder.add(videoFrame, false, encodeOptions);
+ return this._encoder.add(videoSample, false, encodeOptions);
}
/** @internal */
@@ -419,13 +422,8 @@ export class CanvasSource extends VideoSource {
throw new TypeError('duration must be a non-negative number.');
}
- const frame = new VideoFrame(this._canvas, {
- timestamp: Math.round(1e6 * timestamp),
- duration: Math.round(1e6 * duration) || undefined, // Drag 0 duration to undefined, glitches some codecs
- alpha: 'discard',
- });
-
- return this._encoder.add(frame, true, encodeOptions);
+ const sample = new VideoSample(this._canvas, { timestamp, duration });
+ return this._encoder.add(sample, true, encodeOptions);
}
/** @internal */
@@ -475,7 +473,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
return;
}
- void this._encoder.add(videoFrame, true);
+ void this._encoder.add(new VideoSample(videoFrame), true);
},
});
@@ -587,31 +585,31 @@ class AudioEncoderWrapper {
constructor(private source: AudioSource, private encodingConfig: AudioEncodingConfig) {}
- async add(audioData: AudioData, shouldClose: boolean) {
+ async add(audioSample: AudioSample, shouldClose: boolean) {
this.source._ensureValidAdd();
// Ensure audio parameters remain constant
if (this.lastNumberOfChannels !== null && this.lastSampleRate !== null) {
if (
- audioData.numberOfChannels !== this.lastNumberOfChannels
- || audioData.sampleRate !== this.lastSampleRate
+ audioSample.numberOfChannels !== this.lastNumberOfChannels
+ || audioSample.sampleRate !== this.lastSampleRate
) {
throw new Error(
`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at`
- + ` ${this.lastSampleRate} Hz, got ${audioData.numberOfChannels} channels at`
- + ` ${audioData.sampleRate} Hz.`,
+ + ` ${this.lastSampleRate} Hz, got ${audioSample.numberOfChannels} channels at`
+ + ` ${audioSample.sampleRate} Hz.`,
);
}
} else {
- this.lastNumberOfChannels = audioData.numberOfChannels;
- this.lastSampleRate = audioData.sampleRate;
+ this.lastNumberOfChannels = audioSample.numberOfChannels;
+ this.lastSampleRate = audioSample.sampleRate;
}
if (!this.encoderInitialized) {
if (this.ensureEncoderPromise) {
await this.ensureEncoderPromise;
} else {
- await this.ensureEncoder(audioData);
+ await this.ensureEncoder(audioSample);
}
}
assert(this.encoderInitialized);
@@ -619,14 +617,14 @@ class AudioEncoderWrapper {
if (this.customEncoder) {
this.customEncoderQueueSize++;
this.lastCustomEncoderPromise = this.lastCustomEncoderPromise.then(() => {
- return this.customEncoder!.encode(audioData);
+ return this.customEncoder!.encode(audioSample);
});
void this.lastCustomEncoderPromise.then(() => {
this.customEncoderQueueSize--;
if (shouldClose) {
- audioData.close();
+ audioSample.close();
}
});
@@ -636,13 +634,15 @@ class AudioEncoderWrapper {
await this.muxer!.mutex.currentPromise; // Allow the writer to apply backpressure
} else if (this.isPcmEncoder) {
- await this.doPcmEncoding(audioData, shouldClose);
+ await this.doPcmEncoding(audioSample, shouldClose);
} else {
assert(this.encoder);
+ const audioData = audioSample.toAudioData();
this.encoder.encode(audioData);
+ audioData.close();
if (shouldClose) {
- audioData.close();
+ audioSample.close();
}
if (this.encoder.encodeQueueSize >= 4) {
@@ -653,12 +653,12 @@ class AudioEncoderWrapper {
}
}
- private async doPcmEncoding(audioData: AudioData, shouldClose: boolean) {
+ private async doPcmEncoding(audioSample: AudioSample, shouldClose: boolean) {
assert(this.outputSampleSize);
assert(this.writeOutputValue);
// Need to extract data from the audio data before we close it
- const { numberOfChannels, numberOfFrames, sampleRate, timestamp } = audioData;
+ const { numberOfChannels, numberOfFrames, sampleRate, timestamp } = audioSample;
const CHUNK_SIZE = 2048;
const outputs: {
@@ -668,7 +668,7 @@ class AudioEncoderWrapper {
// Prepare all of the output buffers, each being bounded by CHUNK_SIZE so we don't generate huge packets
for (let frame = 0; frame < numberOfFrames; frame += CHUNK_SIZE) {
- const frameCount = Math.min(CHUNK_SIZE, audioData.numberOfFrames - frame);
+ const frameCount = Math.min(CHUNK_SIZE, audioSample.numberOfFrames - frame);
const outputSize = frameCount * numberOfChannels * this.outputSampleSize;
const outputBuffer = new ArrayBuffer(outputSize);
const outputView = new DataView(outputBuffer);
@@ -676,12 +676,11 @@ class AudioEncoderWrapper {
outputs.push({ frameCount, view: outputView });
}
- // All user agents are required to support conversion to f32-planar
- const allocationSize = audioData.allocationSize(({ planeIndex: 0, format: 'f32-planar' }));
+ const allocationSize = audioSample.allocationSize(({ planeIndex: 0, format: 'f32-planar' }));
const floats = new Float32Array(allocationSize / Float32Array.BYTES_PER_ELEMENT);
for (let i = 0; i < numberOfChannels; i++) {
- audioData.copyTo(floats, { planeIndex: i, format: 'f32-planar' });
+ audioSample.copyTo(floats, { planeIndex: i, format: 'f32-planar' });
for (let j = 0; j < outputs.length; j++) {
const { frameCount, view } = outputs[j]!;
@@ -697,7 +696,7 @@ class AudioEncoderWrapper {
}
if (shouldClose) {
- audioData.close();
+ audioSample.close();
}
const meta: EncodedAudioChunkMetadata = {
@@ -716,7 +715,7 @@ class AudioEncoderWrapper {
const packet = new EncodedPacket(
new Uint8Array(outputBuffer),
'key',
- timestamp / 1e6 + startFrame / sampleRate,
+ timestamp + startFrame / sampleRate,
frameCount / sampleRate,
);
@@ -725,7 +724,7 @@ class AudioEncoderWrapper {
}
}
- private async ensureEncoder(audioData: AudioData) {
+ private async ensureEncoder(audioSample: AudioSample) {
if (this.encoderInitialized) {
return;
}
@@ -733,7 +732,7 @@ class AudioEncoderWrapper {
const { promise, resolve } = promiseWithResolvers();
this.ensureEncoderPromise = promise;
- const { numberOfChannels, sampleRate } = audioData;
+ const { numberOfChannels, sampleRate } = audioSample;
const bitrate = this.encodingConfig.bitrate instanceof Quality
? this.encodingConfig.bitrate._toAudioBitrate(this.encodingConfig.codec)
: this.encodingConfig.bitrate;
@@ -903,7 +902,7 @@ class AudioEncoderWrapper {
}
/** @public */
-export class AudioDataSource extends AudioSource {
+export class AudioSampleSource extends AudioSource {
/** @internal */
private _encoder: AudioEncoderWrapper;
@@ -914,12 +913,12 @@ export class AudioDataSource extends AudioSource {
this._encoder = new AudioEncoderWrapper(this, encodingConfig);
}
- add(audioData: AudioData) {
- if (!(audioData instanceof AudioData)) {
- throw new TypeError('audioData must be an AudioData.');
+ add(audioSample: AudioSample) {
+ if (!(audioSample instanceof AudioSample)) {
+ throw new TypeError('audioSample must be an AudioSample.');
}
- return this._encoder.add(audioData, false);
+ return this._encoder.add(audioSample, false);
}
/** @internal */
@@ -972,16 +971,16 @@ export class AudioBufferSource extends AudioSource {
);
}
- const audioData = new AudioData({
+ const audioSample = new AudioSample({
format: 'f32-planar',
sampleRate,
numberOfFrames: framesToCopy,
numberOfChannels,
- timestamp: (1e6 * (this._accumulatedFrameCount + currentRelativeFrame)) / sampleRate,
+ timestamp: (this._accumulatedFrameCount + currentRelativeFrame) / sampleRate,
data: chunkData,
});
- promises.push(this._encoder.add(audioData, true));
+ promises.push(this._encoder.add(audioSample, true));
currentRelativeFrame += framesToCopy;
remainingFrames -= framesToCopy;
@@ -1033,7 +1032,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
return;
}
- void this._encoder.add(audioData, true);
+ void this._encoder.add(new AudioSample(audioData), true);
},
});
diff --git a/src/misc.ts b/src/misc.ts
index 4eb5baf..e4d8b19 100644
--- a/src/misc.ts
+++ b/src/misc.ts
@@ -63,8 +63,12 @@ export const toUint8Array = (source: AllowSharedBufferSource): Uint8Array => {
}
};
-export const toDataView = (bytes: Uint8Array) => {
- return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
+export const toDataView = (source: AllowSharedBufferSource) => {
+ if (source instanceof ArrayBuffer) {
+ return new DataView(source);
+ } else {
+ return new DataView(source.buffer, source.byteOffset, source.byteLength);
+ }
};
export const textEncoder = new TextEncoder();
@@ -356,20 +360,6 @@ export const clamp = (value: number, min: number, max: number) => {
export const UNDETERMINED_LANGUAGE = 'und';
-/** @public */
-export const setVideoFrameTiming = (frame: VideoFrame, timing: {
- timestamp?: number;
- duration?: number;
-}) => {
- const clone = new VideoFrame(frame, {
- timestamp: timing.timestamp !== undefined ? 1e6 * timing.timestamp : undefined,
- duration: timing.duration !== undefined ? 1e6 * timing.duration : undefined,
- });
- frame.close();
-
- return clone;
-};
-
export const roundToPrecision = (value: number, digits: number) => {
const factor = 10 ** digits;
return Math.round(value * factor) / factor;
@@ -393,5 +383,8 @@ export const isIso639Dash2LanguageCode = (x: string) => {
return ISO_639_2_REGEX.test(x);
};
-// Since the result will be floored, add a bit of eps to compensate for floating point errors
+// Since the result will be truncated, add a bit of eps to compensate for floating point errors
export const SECOND_TO_MICROSECOND_FACTOR = 1e6 * (1 + Number.EPSILON);
+
+/** @public */
+export type SetRequired = T & Required>;
diff --git a/src/packet.ts b/src/packet.ts
index 5212176..6c968ef 100644
--- a/src/packet.ts
+++ b/src/packet.ts
@@ -40,11 +40,11 @@ export class EncodedPacket {
}
get microsecondTimestamp() {
- return Math.floor(SECOND_TO_MICROSECOND_FACTOR * this.timestamp);
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp);
}
get microsecondDuration() {
- return Math.floor(SECOND_TO_MICROSECOND_FACTOR * this.duration);
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration);
}
toEncodedVideoChunk() {
diff --git a/src/sample.ts b/src/sample.ts
index 3563120..fa3d577 100644
--- a/src/sample.ts
+++ b/src/sample.ts
@@ -1,196 +1,916 @@
-export const PLACEHOLDER_DATA = new Uint8Array(0);
+import {
+ assert,
+ clamp,
+ isAllowSharedBufferSource,
+ Rotation,
+ SECOND_TO_MICROSECOND_FACTOR,
+ toDataView,
+ toUint8Array,
+ SetRequired,
+} from './misc';
/** @public */
-export type SampleType = 'key' | 'delta';
+export type VideoSampleInit = {
+ format?: VideoPixelFormat;
+ codedWidth?: number;
+ codedHeight?: number;
+ rotation?: Rotation;
+ timestamp?: number;
+ duration?: number;
+ colorSpace?: VideoColorSpaceInit;
+};
/** @public */
-export class EncodedVideoSample {
- constructor(
- public readonly data: Uint8Array,
- public readonly type: SampleType,
- public readonly timestamp: number,
- public readonly duration: number,
- public readonly sequenceNumber = -1,
- public readonly byteLength = data.byteLength,
- ) {
- if (!(data instanceof Uint8Array)) {
- throw new TypeError('data must be a Uint8Array.');
- }
- if (type !== 'key' && type !== 'delta') {
- throw new TypeError('type must be either "key" or "delta".');
- }
- if (!Number.isFinite(timestamp)) {
- throw new TypeError('timestamp must be a number.');
- }
- if (!Number.isFinite(duration) || duration < 0) {
- throw new TypeError('duration must be a non-negative number.');
- }
- if (!Number.isInteger(byteLength) || byteLength < 0) {
- throw new TypeError('byteLength must be a non-negative integer.');
- }
+export class VideoSample {
+ /** @internal */
+ _data!: VideoFrame | OffscreenCanvas | Uint8Array | null;
+ /** @internal */
+ _closed: boolean = false;
+
+ readonly format!: VideoPixelFormat | null;
+ readonly codedWidth!: number;
+ readonly codedHeight!: number;
+ readonly rotation!: Rotation;
+ readonly timestamp!: number;
+ readonly duration!: number;
+ readonly colorSpace!: VideoColorSpace;
+
+ get displayWidth() {
+ return this.rotation % 180 === 0 ? this.codedWidth : this.codedHeight;
}
- get isMetadataOnly() {
- return this.data === PLACEHOLDER_DATA;
+ get displayHeight() {
+ return this.rotation % 180 === 0 ? this.codedHeight : this.codedWidth;
}
get microsecondTimestamp() {
- return Math.floor(1e6 * this.timestamp);
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp);
}
get microsecondDuration() {
- return Math.floor(1e6 * this.duration);
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration);
}
- toEncodedVideoChunk() {
- if (this.isMetadataOnly) {
- throw new TypeError('Metadata-only samples cannot be converted to a chunk.');
- }
+ constructor(data: VideoFrame, init?: VideoSampleInit);
+ constructor(data: CanvasImageSource, init: SetRequired);
+ constructor(
+ data: BufferSource,
+ init: SetRequired
+ );
+ constructor(
+ data: VideoFrame | CanvasImageSource | BufferSource,
+ init?: VideoSampleInit,
+ ) {
+ if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
+ if (!init || typeof init !== 'object') {
+ throw new TypeError('init must be an object.');
+ }
+ if (!('format' in init) || typeof init.format !== 'string') {
+ throw new TypeError('init.format must be a string.');
+ }
+ if (!Number.isInteger(init.codedWidth) || init.codedWidth! <= 0) {
+ throw new TypeError('init.codedWidth must be a positive integer.');
+ }
+ if (!Number.isInteger(init.codedHeight) || init.codedHeight! <= 0) {
+ throw new TypeError('init.codedHeight must be a positive integer.');
+ }
+ if (init.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
+ throw new TypeError('init.rotation, when provided, must be 0, 90, 180, or 270.');
+ }
+ if (!Number.isFinite(init.timestamp)) {
+ throw new TypeError('init.timestamp must be a number.');
+ }
+ if (init.duration !== undefined && (!Number.isFinite(init.duration) || init.duration < 0)) {
+ throw new TypeError('init.duration, when provided, must be a non-negative number.');
+ }
- if (typeof EncodedVideoChunk === 'undefined') {
- throw new Error('Your browser does not support EncodedVideoChunk.');
- }
+ this._data = toUint8Array(data).slice(); // Copy it
- return new EncodedVideoChunk({
- data: this.data,
- type: this.type,
- timestamp: this.microsecondTimestamp,
- duration: this.microsecondDuration,
- });
+ this.format = init.format;
+ this.codedWidth = init.codedWidth!;
+ this.codedHeight = init.codedHeight!;
+ this.rotation = init.rotation ?? 0;
+ this.timestamp = init.timestamp!;
+ this.duration = init.duration ?? 0;
+ this.colorSpace = new VideoColorSpace(init.colorSpace);
+ } else if (typeof VideoFrame !== 'undefined' && data instanceof VideoFrame) {
+ if (init?.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
+ throw new TypeError('init.rotation, when provided, must be 0, 90, 180, or 270.');
+ }
+ if (init?.timestamp !== undefined && !Number.isFinite(init?.timestamp)) {
+ throw new TypeError('init.timestamp, when provided, must be a number.');
+ }
+ if (init?.duration !== undefined && (!Number.isFinite(init.duration) || init.duration < 0)) {
+ throw new TypeError('init.duration, when provided, must be a non-negative number.');
+ }
+
+ this._data = data;
+
+ this.format = data.format;
+ this.codedWidth = data.codedWidth;
+ this.codedHeight = data.codedHeight;
+ this.rotation = init?.rotation ?? 0;
+ this.timestamp = init?.timestamp ?? data.timestamp / 1e6;
+ this.duration = init?.duration ?? (data.duration ?? 0) / 1e6;
+ this.colorSpace = data.colorSpace;
+ } else if (
+ (typeof HTMLImageElement !== 'undefined' && data instanceof HTMLImageElement)
+ || (typeof SVGImageElement !== 'undefined' && data instanceof SVGImageElement)
+ || (typeof ImageBitmap !== 'undefined' && data instanceof ImageBitmap)
+ || (typeof HTMLVideoElement !== 'undefined' && data instanceof HTMLVideoElement)
+ || (typeof HTMLCanvasElement !== 'undefined' && data instanceof HTMLCanvasElement)
+ || (typeof OffscreenCanvas !== 'undefined' && data instanceof OffscreenCanvas)
+ ) {
+ if (!init || typeof init !== 'object') {
+ throw new TypeError('init must be an object.');
+ }
+ if (init.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
+ throw new TypeError('init.rotation, when provided, must be 0, 90, 180, or 270.');
+ }
+ if (!Number.isFinite(init.timestamp)) {
+ throw new TypeError('init.timestamp must be a number.');
+ }
+ if (init.duration !== undefined && (!Number.isFinite(init.duration) || init.duration < 0)) {
+ throw new TypeError('init.duration, when provided, must be a non-negative number.');
+ }
+
+ if (typeof VideoFrame !== 'undefined') {
+ return new VideoSample(
+ new VideoFrame(data, {
+ timestamp: Math.trunc(init.timestamp! * SECOND_TO_MICROSECOND_FACTOR),
+ duration: Math.trunc((init.duration ?? 0) * SECOND_TO_MICROSECOND_FACTOR),
+ }),
+ init,
+ );
+ }
+
+ let width = 0;
+ let height = 0;
+
+ // Determine the dimensions of the thing
+ if ('naturalWidth' in data) {
+ width = data.naturalWidth;
+ height = data.naturalHeight;
+ } else if ('videoWidth' in data) {
+ width = data.videoWidth;
+ height = data.videoHeight;
+ } else if ('width' in data) {
+ width = Number(data.width);
+ height = Number(data.height);
+ }
+
+ if (!width || !height) {
+ throw new TypeError('Could not determine dimensions.');
+ }
+
+ const canvas = new OffscreenCanvas(width, height);
+ const context = canvas.getContext('2d', { alpha: false, willReadFrequently: true });
+ assert(context);
+
+ // Draw it to a canvas
+ context.drawImage(data, 0, 0);
+ this._data = canvas;
+
+ this.format = 'RGBX';
+ this.codedWidth = width;
+ this.codedHeight = height;
+ this.rotation = init.rotation ?? 0;
+ this.timestamp = init.timestamp!;
+ this.duration = init.duration ?? 0;
+ this.colorSpace = new VideoColorSpace({
+ matrix: 'rgb',
+ primaries: 'bt709',
+ transfer: 'iec61966-2-1',
+ fullRange: true,
+ });
+ } else {
+ throw new TypeError('Invalid data type: Must be a BufferSource or CanvasImageSource.');
+ }
}
- clone(options?: {
- timestamp?: number;
- duration?: number;
- }) {
- if (options !== undefined && (!options || typeof options !== 'object')) {
- throw new TypeError('options, when provided, must be an object.');
- }
- if (options?.timestamp !== undefined && !Number.isFinite(options.timestamp)) {
- throw new TypeError('options.timestamp, when provided, must be a number.');
- }
- if (options?.duration !== undefined && !Number.isFinite(options.duration)) {
- throw new TypeError('options.duration, when provided, must be a number.');
+ clone() {
+ if (this._closed) {
+ throw new Error('VideoSample is closed.');
}
- return new EncodedVideoSample(
- this.data,
- this.type,
- options?.timestamp ?? this.timestamp,
- options?.duration ?? this.duration,
- this.byteLength,
+ assert(this._data !== null);
+
+ if (isVideoFrame(this._data)) {
+ return new VideoSample(this._data.clone());
+ } else if (this._data instanceof Uint8Array) {
+ return new VideoSample(this._data.slice(), {
+ format: this.format!,
+ codedWidth: this.codedWidth,
+ codedHeight: this.codedHeight,
+ timestamp: this.timestamp,
+ duration: this.duration,
+ colorSpace: this.colorSpace,
+ });
+ } else {
+ return new VideoSample(this._data, {
+ format: this.format!,
+ codedWidth: this.codedWidth,
+ codedHeight: this.codedHeight,
+ timestamp: this.timestamp,
+ duration: this.duration,
+ colorSpace: this.colorSpace,
+ });
+ }
+ }
+
+ close() {
+ if (this._closed) {
+ return;
+ }
+
+ if (isVideoFrame(this._data)) {
+ this._data.close();
+ } else {
+ this._data = null; // GC that shit
+ }
+
+ this._closed = true;
+ }
+
+ allocationSize() {
+ if (this._closed) {
+ throw new Error('VideoSample is closed.');
+ }
+
+ assert(this._data !== null);
+
+ if (isVideoFrame(this._data)) {
+ return this._data.allocationSize();
+ } else if (this._data instanceof Uint8Array) {
+ return this._data.byteLength;
+ } else {
+ return this.codedWidth * this.codedHeight * 4; // RGBX
+ }
+ }
+
+ async copyTo(destination: AllowSharedBufferSource) {
+ if (!isAllowSharedBufferSource(destination)) {
+ throw new TypeError('destination must be an ArrayBuffer or an ArrayBuffer view.');
+ }
+
+ if (this._closed) {
+ throw new Error('VideoSample is closed.');
+ }
+
+ assert(this._data !== null);
+
+ if (isVideoFrame(this._data)) {
+ await this._data.copyTo(destination);
+ } else if (this._data instanceof Uint8Array) {
+ const dest = toUint8Array(destination);
+ dest.set(this._data);
+ } else {
+ const canvas = this._data;
+ const context = canvas.getContext('2d', { alpha: false });
+ assert(context);
+
+ const imageData = context.getImageData(0, 0, this.codedWidth, this.codedHeight);
+ const dest = toUint8Array(destination);
+ dest.set(imageData.data);
+ }
+ }
+
+ toVideoFrame() {
+ if (this._closed) {
+ throw new Error('VideoSample is closed.');
+ }
+
+ assert(this._data !== null);
+
+ if (isVideoFrame(this._data)) {
+ return new VideoFrame(this._data, {
+ timestamp: this.microsecondTimestamp,
+ duration: this.microsecondDuration || undefined, // Drag 0 duration to undefined, glitches some codecs
+ });
+ } else if (this._data instanceof Uint8Array) {
+ return new VideoFrame(this._data, {
+ format: this.format!,
+ codedWidth: this.codedWidth,
+ codedHeight: this.codedHeight,
+ timestamp: this.microsecondTimestamp,
+ duration: this.microsecondDuration,
+ colorSpace: this.colorSpace,
+ });
+ } else {
+ return new VideoFrame(this._data, {
+ timestamp: this.microsecondTimestamp,
+ duration: this.microsecondDuration,
+ });
+ }
+ }
+
+ draw(
+ context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
+ dx: number,
+ dy: number,
+ dWidth: number = this.displayWidth,
+ dHeight: number = this.displayHeight,
+ ) {
+ if (!(
+ (typeof CanvasRenderingContext2D !== 'undefined' && context instanceof CanvasRenderingContext2D)
+ || (
+ typeof OffscreenCanvasRenderingContext2D !== 'undefined'
+ && context instanceof OffscreenCanvasRenderingContext2D
+ )
+ )) {
+ throw new TypeError('context must be a CanvasRenderingContext2D or OffscreenCanvasRenderingContext2D.');
+ }
+ if (!Number.isFinite(dx)) {
+ throw new TypeError('dx must be a number.');
+ }
+ if (!Number.isFinite(dy)) {
+ throw new TypeError('dy must be a number.');
+ }
+ if (!Number.isFinite(dWidth) || dWidth < 0) {
+ throw new TypeError('dWidth must be a non-negative number.');
+ }
+ if (!Number.isFinite(dHeight) || dHeight < 0) {
+ throw new TypeError('dHeight must be a non-negative number.');
+ }
+
+ if (this._closed) {
+ throw new Error('VideoSample is closed.');
+ }
+
+ const source = this.toCanvasImageSource();
+
+ context.save();
+
+ const centerX = dx + dWidth / 2;
+ const centerY = dy + dHeight / 2;
+
+ context.translate(centerX, centerY);
+ context.rotate(this.rotation * Math.PI / 180);
+
+ const aspectRatioChange = this.rotation % 180 === 0 ? 1 : dWidth / dHeight;
+
+ // Scale to compensate for aspect ratio changes when rotated
+ context.scale(1 / aspectRatioChange, aspectRatioChange);
+
+ context.drawImage(
+ source,
+ -dWidth / 2,
+ -dHeight / 2,
+ dWidth,
+ dHeight,
);
+
+ // Restore the previous transformation state
+ context.restore();
}
- static fromEncodedVideoChunk(chunk: EncodedVideoChunk) {
- if (!(chunk instanceof EncodedVideoChunk)) {
- throw new TypeError('chunk must be an EncodedVideoChunk.');
+ toCanvasImageSource() {
+ if (this._closed) {
+ throw new Error('VideoSample is closed.');
}
- const data = new Uint8Array(chunk.byteLength);
- chunk.copyTo(data);
+ assert(this._data !== null);
- return new EncodedVideoSample(
- data,
- chunk.type,
- chunk.timestamp / 1e6,
- (chunk.duration ?? 0) / 1e6,
- );
+ if (this._data instanceof Uint8Array) {
+ // Requires VideoFrame to be defined
+ return this.toVideoFrame();
+ } else {
+ return this._data;
+ }
+ }
+
+ setRotation(newRotation: Rotation) {
+ if (![0, 90, 180, 270].includes(newRotation)) {
+ throw new TypeError('newRotation must be 0, 90, 180, or 270.');
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+ (this.rotation as Rotation) = newRotation;
+ }
+
+ setTimestamp(newTimestamp: number) {
+ if (!Number.isFinite(newTimestamp)) {
+ throw new TypeError('newTimestamp must be a number.');
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+ (this.timestamp as number) = newTimestamp;
+ }
+
+ setDuration(newDuration: number) {
+ if (!Number.isFinite(newDuration) || newDuration < 0) {
+ throw new TypeError('newDuration must be a non-negative number.');
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+ (this.duration as number) = newDuration;
}
}
+const isVideoFrame = (x: unknown): x is VideoFrame => {
+ return typeof VideoFrame !== 'undefined' && x instanceof VideoFrame;
+};
+
+const AUDIO_SAMPLE_FORMATS = new Set(
+ ['f32', 'f32-planar', 's16', 's16-planar', 's32', 's32-planar', 'u8', 'u8-planar'],
+);
+
/** @public */
-export class EncodedAudioSample {
- constructor(
- public readonly data: Uint8Array,
- public readonly type: SampleType,
- public readonly timestamp: number,
- public readonly duration: number,
- public readonly sequenceNumber = -1,
- public readonly byteLength = data.byteLength,
- ) {
- if (!(data instanceof Uint8Array)) {
- throw new TypeError('data must be a Uint8Array.');
- }
- if (type !== 'key' && type !== 'delta') {
- throw new TypeError('type must be either "key" or "delta".');
- }
- if (!Number.isFinite(timestamp)) {
- throw new TypeError('timestamp must be a number.');
- }
- if (!Number.isFinite(duration) || duration < 0) {
- throw new TypeError('duration must be a non-negative number.');
- }
- if (!Number.isInteger(byteLength) || byteLength < 0) {
- throw new TypeError('byteLength must be a non-negative integer.');
- }
- }
+export type AudioSampleInit = {
+ data: AllowSharedBufferSource;
+ format: AudioSampleFormat;
+ numberOfChannels: number;
+ sampleRate: number;
+ timestamp: number;
+};
- get isMetadataOnly() {
- return this.data === PLACEHOLDER_DATA;
- }
+/** @public */
+export class AudioSample {
+ /** @internal */
+ _data: AudioData | Uint8Array;
+ /** @internal */
+ _closed: boolean = false;
+
+ readonly format: AudioSampleFormat;
+ readonly sampleRate: number;
+ readonly numberOfFrames: number;
+ readonly numberOfChannels: number;
+ readonly duration: number;
+ readonly timestamp: number;
get microsecondTimestamp() {
- return Math.floor(1e6 * this.timestamp);
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp);
}
get microsecondDuration() {
- return Math.floor(1e6 * this.duration);
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration);
}
- toEncodedAudioChunk() {
- if (this.isMetadataOnly) {
- throw new TypeError('Metadata-only samples cannot be converted to a chunk.');
- }
+ constructor(init: AudioData | AudioSampleInit) {
+ if (isAudioData(init)) {
+ if (init.format === null) {
+ throw new TypeError('AudioData with null format is not supported.');
+ }
- if (typeof EncodedAudioChunk === 'undefined') {
- throw new Error('Your browser does not support EncodedAudioChunk.');
- }
+ this._data = init;
- return new EncodedAudioChunk({
- data: this.data,
- type: this.type,
- timestamp: this.microsecondTimestamp,
- duration: this.microsecondDuration,
- });
+ this.format = init.format;
+ this.sampleRate = init.sampleRate;
+ this.numberOfFrames = init.numberOfFrames;
+ this.numberOfChannels = init.numberOfChannels;
+ this.timestamp = init.timestamp / 1e6;
+ this.duration = init.numberOfFrames / init.sampleRate;
+ } else {
+ if (!init || typeof init !== 'object') {
+ throw new TypeError('Invalid AudioDataInit: must be an object.');
+ }
+
+ if (!AUDIO_SAMPLE_FORMATS.has(init.format)) {
+ throw new TypeError('Invalid AudioDataInit: invalid format.');
+ }
+ if (!Number.isFinite(init.sampleRate) || init.sampleRate <= 0) {
+ throw new TypeError('Invalid AudioDataInit: sampleRate must be > 0.');
+ }
+ if (!Number.isInteger(init.numberOfChannels) || init.numberOfChannels === 0) {
+ throw new TypeError('Invalid AudioDataInit: numberOfChannels must be an integer > 0.');
+ }
+ if (!Number.isFinite(init?.timestamp)) {
+ throw new TypeError('init.timestamp must be a number.');
+ }
+
+ const numberOfFrames
+ = init.data.byteLength / (getBytesPerSample(init.format) * init.numberOfChannels);
+ if (!Number.isInteger(numberOfFrames)) {
+ throw new TypeError('Invalid AudioDataInit: data size is not a multiple of frame size.');
+ }
+
+ this.format = init.format;
+ this.sampleRate = init.sampleRate;
+ this.numberOfFrames = numberOfFrames;
+ this.numberOfChannels = init.numberOfChannels;
+ this.timestamp = init.timestamp;
+ this.duration = numberOfFrames / init.sampleRate;
+
+ let dataBuffer: Uint8Array;
+ if (init.data instanceof ArrayBuffer) {
+ dataBuffer = new Uint8Array(init.data);
+ } else if (ArrayBuffer.isView(init.data)) {
+ dataBuffer = new Uint8Array(init.data.buffer, init.data.byteOffset, init.data.byteLength);
+ } else {
+ throw new TypeError('Invalid AudioDataInit: data is not a BufferSource.');
+ }
+
+ const expectedSize
+ = this.numberOfFrames * this.numberOfChannels * getBytesPerSample(this.format);
+ if (dataBuffer.byteLength < expectedSize) {
+ throw new TypeError('Invalid AudioDataInit: insufficient data size.');
+ }
+
+ this._data = dataBuffer;
+ }
}
- clone(options?: {
- timestamp?: number;
- duration?: number;
+ allocationSize(options: {
+ planeIndex: number;
+ format?: AudioSampleFormat;
+ frameOffset?: number;
+ frameCount?: number;
}) {
- if (options !== undefined && (!options || typeof options !== 'object')) {
- throw new TypeError('options, when provided, must be an object.');
+ if (!options || typeof options !== 'object') {
+ throw new TypeError('options must be an object.');
}
- if (options?.timestamp !== undefined && !Number.isFinite(options.timestamp)) {
- throw new TypeError('options.timestamp, when provided, must be a number.');
- }
- if (options?.duration !== undefined && !Number.isFinite(options.duration)) {
- throw new TypeError('options.duration, when provided, must be a number.');
+ if (!Number.isInteger(options.planeIndex) || options.planeIndex < 0) {
+ throw new TypeError('planeIndex must be a non-negative integer.');
}
- return new EncodedAudioSample(
- this.data,
- this.type,
- options?.timestamp ?? this.timestamp,
- options?.duration ?? this.duration,
- this.byteLength,
- );
+ if (options.format !== undefined && !AUDIO_SAMPLE_FORMATS.has(options.format)) {
+ throw new TypeError('Invalid format.');
+ }
+ if (options.frameOffset !== undefined && (!Number.isInteger(options.frameOffset) || options.frameOffset < 0)) {
+ throw new TypeError('frameOffset must be a non-negative integer.');
+ }
+ if (options.frameCount !== undefined && (!Number.isInteger(options.frameCount) || options.frameCount < 0)) {
+ throw new TypeError('frameCount must be a non-negative integer.');
+ }
+
+ if (this._closed) {
+ throw new Error('AudioSample is closed.');
+ }
+
+ const destFormat = options.format ?? this.format;
+
+ const frameOffset = options.frameOffset ?? 0;
+ if (frameOffset >= this.numberOfFrames) {
+ throw new RangeError('frameOffset out of range');
+ }
+
+ const copyFrameCount
+ = options.frameCount !== undefined ? options.frameCount : (this.numberOfFrames - frameOffset);
+ if (copyFrameCount > (this.numberOfFrames - frameOffset)) {
+ throw new RangeError('frameCount out of range');
+ }
+
+ const bytesPerSample = getBytesPerSample(destFormat);
+ const isPlanar = formatIsPlanar(destFormat);
+ if (isPlanar && options.planeIndex >= this.numberOfChannels) {
+ throw new RangeError('planeIndex out of range');
+ }
+ if (!isPlanar && options.planeIndex !== 0) {
+ throw new RangeError('planeIndex out of range');
+ }
+
+ const elementCount = isPlanar ? copyFrameCount : copyFrameCount * this.numberOfChannels;
+ return elementCount * bytesPerSample;
}
- static fromEncodedAudioChunk(chunk: EncodedAudioChunk) {
- if (!(chunk instanceof EncodedAudioChunk)) {
- throw new TypeError('chunk must be an EncodedAudioChunk.');
+ copyTo(
+ destination: AllowSharedBufferSource,
+ options: {
+ planeIndex: number;
+ format?: AudioSampleFormat;
+ frameOffset?: number;
+ frameCount?: number;
+ },
+ ) {
+ if (!isAllowSharedBufferSource(destination)) {
+ throw new TypeError('destination must be an ArrayBuffer or an ArrayBuffer view.');
+ }
+ if (!options || typeof options !== 'object') {
+ throw new TypeError('options must be an object.');
+ }
+ if (!Number.isInteger(options.planeIndex) || options.planeIndex < 0) {
+ throw new TypeError('planeIndex must be a non-negative integer.');
}
- const data = new Uint8Array(chunk.byteLength);
- chunk.copyTo(data);
+ if (options.format !== undefined && !AUDIO_SAMPLE_FORMATS.has(options.format)) {
+ throw new TypeError('Invalid format.');
+ }
+ if (options.frameOffset !== undefined && (!Number.isInteger(options.frameOffset) || options.frameOffset < 0)) {
+ throw new TypeError('frameOffset must be a non-negative integer.');
+ }
+ if (options.frameCount !== undefined && (!Number.isInteger(options.frameCount) || options.frameCount < 0)) {
+ throw new TypeError('frameCount must be a non-negative integer.');
+ }
- return new EncodedAudioSample(
- data,
- chunk.type as SampleType, // Typing is weird
- chunk.timestamp / 1e6,
- (chunk.duration ?? 0) / 1e6,
- );
+ if (this._closed) {
+ throw new Error('AudioSample is closed.');
+ }
+
+ const { planeIndex, format, frameCount: optFrameCount, frameOffset: optFrameOffset } = options;
+
+ const destFormat = format ?? this.format;
+ if (!destFormat) throw new Error('Destination format not determined');
+
+ const numFrames = this.numberOfFrames;
+ const numChannels = this.numberOfChannels;
+ const frameOffset = optFrameOffset ?? 0;
+ if (frameOffset >= numFrames) {
+ throw new RangeError('frameOffset out of range');
+ }
+
+ const copyFrameCount = optFrameCount !== undefined ? optFrameCount : (numFrames - frameOffset);
+ if (copyFrameCount > (numFrames - frameOffset)) {
+ throw new RangeError('frameCount out of range');
+ }
+
+ const destBytesPerSample = getBytesPerSample(destFormat);
+ const destIsPlanar = formatIsPlanar(destFormat);
+ if (destIsPlanar && planeIndex >= numChannels) {
+ throw new RangeError('planeIndex out of range');
+ }
+ if (!destIsPlanar && planeIndex !== 0) {
+ throw new RangeError('planeIndex out of range');
+ }
+
+ const destElementCount = destIsPlanar ? copyFrameCount : copyFrameCount * numChannels;
+ const requiredSize = destElementCount * destBytesPerSample;
+ if (destination.byteLength < requiredSize) {
+ throw new RangeError('Destination buffer is too small');
+ }
+
+ const destView = toDataView(destination);
+ const writeFn = getWriteFunction(destFormat);
+
+ if (isAudioData(this._data)) {
+ if (destIsPlanar) {
+ if (destFormat === 'f32-planar') {
+ // Simple, since the browser must support f32-planar, we can just delegate here
+ this._data.copyTo(destination, {
+ planeIndex,
+ frameOffset,
+ frameCount: copyFrameCount,
+ format: 'f32-planar',
+ });
+ } else {
+ // Allocate temporary buffer for f32-planar data
+ const tempBuffer = new ArrayBuffer(copyFrameCount * 4);
+ const tempArray = new Float32Array(tempBuffer);
+ this._data.copyTo(tempArray, {
+ planeIndex,
+ frameOffset,
+ frameCount: copyFrameCount,
+ format: 'f32-planar',
+ });
+
+ // Convert each f32 sample to destination format
+ const tempView = new DataView(tempBuffer);
+ for (let i = 0; i < copyFrameCount; i++) {
+ const destOffset = i * destBytesPerSample;
+ const sample = tempView.getFloat32(i * 4, true);
+ writeFn(destView, destOffset, sample);
+ }
+ }
+ } else {
+ // Destination is interleaved.
+ // Allocate a temporary Float32Array to hold one channel's worth of data.
+ const numCh = numChannels;
+ const temp = new Float32Array(copyFrameCount);
+ for (let ch = 0; ch < numCh; ch++) {
+ this._data.copyTo(temp, {
+ planeIndex: ch,
+ frameOffset,
+ frameCount: copyFrameCount,
+ format: 'f32-planar',
+ });
+ for (let i = 0; i < copyFrameCount; i++) {
+ const destIndex = i * numCh + ch;
+ const destOffset = destIndex * destBytesPerSample;
+ writeFn(destView, destOffset, temp[i]!);
+ }
+ }
+ }
+ } else {
+ // Branch for Uint8Array data (non-AudioData)
+ const uint8Data = this._data;
+ const srcView = new DataView(uint8Data.buffer, uint8Data.byteOffset, uint8Data.byteLength);
+
+ const srcFormat = this.format;
+ const readFn = getReadFunction(srcFormat);
+ const srcBytesPerSample = getBytesPerSample(srcFormat);
+ const srcIsPlanar = formatIsPlanar(srcFormat);
+
+ for (let i = 0; i < copyFrameCount; i++) {
+ if (destIsPlanar) {
+ const destOffset = i * destBytesPerSample;
+ let srcOffset: number;
+ if (srcIsPlanar) {
+ srcOffset = (planeIndex * numFrames + (i + frameOffset)) * srcBytesPerSample;
+ } else {
+ srcOffset = (((i + frameOffset) * numChannels) + planeIndex) * srcBytesPerSample;
+ }
+
+ const normalized = readFn(srcView, srcOffset);
+ writeFn(destView, destOffset, normalized);
+ } else {
+ for (let ch = 0; ch < numChannels; ch++) {
+ const destIndex = i * numChannels + ch;
+ const destOffset = destIndex * destBytesPerSample;
+ let srcOffset: number;
+ if (srcIsPlanar) {
+ srcOffset = (ch * numFrames + (i + frameOffset)) * srcBytesPerSample;
+ } else {
+ srcOffset = (((i + frameOffset) * numChannels) + ch) * srcBytesPerSample;
+ }
+
+ const normalized = readFn(srcView, srcOffset);
+ writeFn(destView, destOffset, normalized);
+ }
+ }
+ }
+ }
+ }
+
+ clone(): AudioSample {
+ if (this._closed) {
+ throw new Error('AudioSample is closed.');
+ }
+
+ if (isAudioData(this._data)) {
+ return new AudioSample(this._data.clone());
+ } else {
+ return new AudioSample({
+
+ format: this.format,
+ sampleRate: this.sampleRate,
+ numberOfFrames: this.numberOfFrames,
+ numberOfChannels: this.numberOfChannels,
+ timestamp: this.timestamp,
+ data: this._data,
+ });
+ }
+ }
+
+ close(): void {
+ if (this._closed) {
+ return;
+ }
+
+ if (isAudioData(this._data)) {
+ this._data.close();
+ } else {
+ this._data = new Uint8Array(0);
+ }
+
+ this._closed = true;
+ }
+
+ toAudioData() {
+ if (this._closed) {
+ throw new Error('AudioSample is closed.');
+ }
+
+ if (isAudioData(this._data)) {
+ if (this._data.timestamp === this.microsecondTimestamp) {
+ // Timestamp matches, let's just return the data (but cloned)
+ return this._data.clone();
+ } else {
+ // It's impossible to simply change an AudioData's timestamp, so we'll need to create a new one
+ if (formatIsPlanar(this.format)) {
+ const size = this.allocationSize({ planeIndex: 0, format: this.format });
+ const data = new ArrayBuffer(size * this.numberOfChannels);
+
+ // We gotta read out each plane individually
+ for (let i = 0; i < this.numberOfChannels; i++) {
+ this.copyTo(new Uint8Array(data, i * size, size), { planeIndex: i, format: this.format });
+ }
+
+ return new AudioData({
+
+ format: this.format,
+ sampleRate: this.sampleRate,
+ numberOfFrames: this.numberOfFrames,
+ numberOfChannels: this.numberOfChannels,
+ timestamp: this.microsecondTimestamp,
+ data,
+ });
+ } else {
+ const data = new ArrayBuffer(this.allocationSize({ planeIndex: 0, format: this.format }));
+
+ this.copyTo(new DataView(data), { planeIndex: 0, format: this.format });
+
+ return new AudioData({
+
+ format: this.format,
+ sampleRate: this.sampleRate,
+ numberOfFrames: this.numberOfFrames,
+ numberOfChannels: this.numberOfChannels,
+ timestamp: this.microsecondTimestamp,
+ data,
+ });
+ }
+ }
+ } else {
+ return new AudioData({
+
+ format: this.format,
+ sampleRate: this.sampleRate,
+ numberOfFrames: this.numberOfFrames,
+ numberOfChannels: this.numberOfChannels,
+ timestamp: this.microsecondTimestamp,
+ data: this._data,
+ });
+ }
+ }
+
+ toAudioBuffer() {
+ if (this._closed) {
+ throw new Error('AudioSample is closed.');
+ }
+
+ const audioBuffer = new AudioBuffer({
+ numberOfChannels: this.numberOfChannels,
+ length: this.numberOfFrames,
+ sampleRate: this.sampleRate,
+ });
+
+ const dataBytes = new Float32Array(this.allocationSize({ planeIndex: 0, format: 'f32-planar' }) / 4);
+
+ for (let i = 0; i < this.numberOfChannels; i++) {
+ this.copyTo(dataBytes, { planeIndex: i, format: 'f32-planar' });
+ audioBuffer.copyToChannel(dataBytes, i);
+ }
+
+ return audioBuffer;
+ }
+
+ setTimestamp(newTimestamp: number) {
+ if (!Number.isFinite(newTimestamp)) {
+ throw new TypeError('newTimestamp must be a number.');
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+ (this.timestamp as number) = newTimestamp;
}
}
+
+const getBytesPerSample = (format: AudioSampleFormat): number => {
+ switch (format) {
+ case 'u8':
+ case 'u8-planar':
+ return 1;
+ case 's16':
+ case 's16-planar':
+ return 2;
+ case 's32':
+ case 's32-planar':
+ return 4;
+ case 'f32':
+ case 'f32-planar':
+ return 4;
+ default:
+ throw new Error('Unknown AudioSampleFormat');
+ }
+};
+
+const formatIsPlanar = (format: AudioSampleFormat): boolean => {
+ switch (format) {
+ case 'u8-planar':
+ case 's16-planar':
+ case 's32-planar':
+ case 'f32-planar':
+ return true;
+ default:
+ return false;
+ }
+};
+
+const getReadFunction = (format: AudioSampleFormat): (view: DataView, offset: number) => number => {
+ switch (format) {
+ case 'u8':
+ case 'u8-planar':
+ return (view, offset) => (view.getUint8(offset) - 128) / 128;
+ case 's16':
+ case 's16-planar':
+ return (view, offset) => view.getInt16(offset, true) / 32768;
+ case 's32':
+ case 's32-planar':
+ return (view, offset) => view.getInt32(offset, true) / 2147483648;
+ case 'f32':
+ case 'f32-planar':
+ return (view, offset) => view.getFloat32(offset, true);
+ }
+};
+
+const getWriteFunction = (format: AudioSampleFormat): (view: DataView, offset: number, value: number) => void => {
+ switch (format) {
+ case 'u8':
+ case 'u8-planar':
+ return (view, offset, value) =>
+ view.setUint8(offset, clamp((value + 1) * 127.5, 0, 255));
+ case 's16':
+ case 's16-planar':
+ return (view, offset, value) =>
+ view.setInt16(offset, clamp(Math.round(value * 32767), -32768, 32767), true);
+ case 's32':
+ case 's32-planar':
+ return (view, offset, value) =>
+ view.setInt32(offset, clamp(Math.round(value * 2147483647), -2147483648, 2147483647), true);
+ case 'f32':
+ case 'f32-planar':
+ return (view, offset, value) => view.setFloat32(offset, value, true);
+ }
+};
+
+const isAudioData = (x: unknown): x is AudioData => {
+ return typeof AudioData !== 'undefined' && x instanceof AudioData;
+};