Compare commits

..
4 Commits
9 changed files with 95 additions and 15 deletions
+2
View File
@@ -49,6 +49,8 @@ Core features include:
npm install mediabunny npm install mediabunny
``` ```
Requires any JavaScript environment that can run ECMAScript 2021 or later. Mediabunny is expected to be run in modern browsers. For types, TypeScript 5.7 or later is required.
### Read file metadata ### Read file metadata
```js ```js
+4
View File
@@ -17,6 +17,10 @@ bun add mediabunny
``` ```
::: :::
::: info
Requires any JavaScript environment that can run ECMAScript 2021 or later. Mediabunny is expected to be run in modern browsers. For types, TypeScript 5.7 or later is required.
:::
Then, simply import it like this: Then, simply import it like this:
```ts ```ts
import { ... } from 'mediabunny'; // ESM import { ... } from 'mediabunny'; // ESM
+12
View File
@@ -286,6 +286,18 @@ See [Media sinks](./media-sinks) for a full list of sinks.
### Examples ### Examples
Loop over all raw encoded packets of a track:
```ts
import { EncodedPacketSink } from 'mediabunny';
const videoTrack = await input.getPrimaryVideoTrack();
const sink = new EncodedPacketSink(videoTrack);
for await (const packet of sink.packets()) {
console.log(packet.timestamp);
}
```
Here we iterate over all samples (frames) of a video track: Here we iterate over all samples (frames) of a video track:
```ts ```ts
import { VideoSampleSink } from 'mediabunny'; import { VideoSampleSink } from 'mediabunny';
+6 -6
View File
@@ -1,16 +1,16 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"version": "1.3.0", "version": "1.3.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mediabunny", "name": "mediabunny",
"version": "1.3.0", "version": "1.3.3",
"license": "MPL-2.0", "license": "MPL-2.0",
"dependencies": { "dependencies": {
"@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-mediacapture-transform": "^0.1.11",
"@types/dom-webcodecs": "^0.1.15" "@types/dom-webcodecs": "0.1.13"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.22.0", "@eslint/js": "^9.22.0",
@@ -2443,9 +2443,9 @@
} }
}, },
"node_modules/@types/dom-webcodecs": { "node_modules/@types/dom-webcodecs": {
"version": "0.1.15", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.15.tgz", "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz",
"integrity": "sha512-omOlCPvTWyPm4ZE5bZUhlSvnHM2ZWM2U+1cPiYFL/e8aV5O9MouELp+L4dMKNTON0nTeHqEg+KWDfFQMY5Wkaw==", "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
+3 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.3.0", "version": "1.3.3",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.", "description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module", "type": "module",
"main": "./dist/bundles/mediabunny.cjs", "main": "./dist/bundles/mediabunny.cjs",
@@ -31,7 +31,7 @@
"docs:preview": "vitepress preview docs", "docs:preview": "vitepress preview docs",
"dev": "vite", "dev": "vite",
"examples:build": "vite build", "examples:build": "vite build",
"fix-build-import-paths": "find dist -name \"*.js\" -type f -exec sed -i '' \"s/ from '\\([^']*\\)';/ from '\\1.js';/g\" {} \\;", "fix-build-import-paths": "tsx scripts/add-import-extensions.ts",
"append-namespace": "echo 'export as namespace Mediabunny;' >> dist/mediabunny.d.ts" "append-namespace": "echo 'export as namespace Mediabunny;' >> dist/mediabunny.d.ts"
}, },
"license": "MPL-2.0", "license": "MPL-2.0",
@@ -49,7 +49,7 @@
}, },
"dependencies": { "dependencies": {
"@types/dom-mediacapture-transform": "^0.1.11", "@types/dom-mediacapture-transform": "^0.1.11",
"@types/dom-webcodecs": "^0.1.15" "@types/dom-webcodecs": "0.1.13"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.22.0", "@eslint/js": "^9.22.0",
+37
View File
@@ -0,0 +1,37 @@
import * as fs from 'fs';
import * as path from 'path';
// .js extensions are technically required in compliant ECMAScript, and Webpack needs them, so we add them here.
const walkDir = (dir: string) => {
const files: string[] = [];
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
files.push(...walkDir(fullPath));
} else if (item.endsWith('.js')) {
files.push(fullPath);
}
}
return files;
};
const fixFile = (filePath: string) => {
const content = fs.readFileSync(filePath, 'utf8');
const fixed = content.replace(
/(\s+from\s+['"])([^'"]*)(['"])/g,
'$1$2.js$3',
);
if (content !== fixed) {
fs.writeFileSync(filePath, fixed);
}
};
const jsFiles = walkDir('dist');
jsFiles.forEach(fixFile);
+1 -1
View File
@@ -1,5 +1,5 @@
import ts from 'typescript'; import ts from 'typescript';
import * as fs from 'node:fs'; import * as fs from 'fs';
const checkDocblocks = (filePath: string) => { const checkDocblocks = (filePath: string) => {
const program = ts.createProgram([filePath], {}); const program = ts.createProgram([filePath], {});
+3 -3
View File
@@ -515,15 +515,15 @@ export const retriedFetch = async (
try { try {
return await fetch(url, requestInit); return await fetch(url, requestInit);
} catch (error) { } catch (error) {
console.error('Retrying failed fetch. Error:', error);
attempts++; attempts++;
const retryDelayInSeconds = getRetryDelay(attempts); const retryDelayInSeconds = getRetryDelay(attempts);
if (retryDelayInSeconds === null) { if (retryDelayInSeconds === null) {
throw error; throw error;
} }
console.error('Retrying failed fetch. Error:', error);
if (!Number.isFinite(retryDelayInSeconds) || retryDelayInSeconds < 0) { if (!Number.isFinite(retryDelayInSeconds) || retryDelayInSeconds < 0) {
throw new TypeError('Retry delay must be a non-negative finite number.'); throw new TypeError('Retry delay must be a non-negative finite number.');
} }
+27 -2
View File
@@ -218,7 +218,8 @@ export class UrlSource extends Source {
const buffer = await response.arrayBuffer(); const buffer = await response.arrayBuffer();
if (!range) { if (response.status === 200) {
// The server didn't return 206 Partial Content, so it's not a range response
this._fullData = buffer; this._fullData = buffer;
} }
@@ -252,6 +253,26 @@ export class UrlSource extends Source {
return this._fullData.byteLength; return this._fullData.byteLength;
} }
// First, try a HEAD request to get the size
try {
const headResponse = await retriedFetch(
this._url,
mergeObjectsDeeply(this._options.requestInit ?? {}, {
method: 'HEAD',
}),
this._options.getRetryDelay ?? (() => null),
);
if (headResponse.ok) {
const contentLength = headResponse.headers.get('Content-Length');
if (contentLength) {
return parseInt(contentLength);
}
}
} catch {
// We tried
}
// Try a range request to get the Content-Range header // Try a range request to get the Content-Range header
const rangeResponse = await retriedFetch( const rangeResponse = await retriedFetch(
this._url, this._url,
@@ -267,9 +288,13 @@ export class UrlSource extends Source {
if (contentRange) { if (contentRange) {
const match = contentRange.match(/bytes \d+-\d+\/(\d+)/); const match = contentRange.match(/bytes \d+-\d+\/(\d+)/);
if (match && match[1]) { if (match && match[1]) {
return parseInt(match[1], 10); return parseInt(match[1]);
} }
} }
} else if (rangeResponse.status === 200) {
// The server just returned the whole thing
this._fullData = await rangeResponse.arrayBuffer();
return this._fullData.byteLength;
} }
// If the range request didn't provide the size, make a full GET request // If the range request didn't provide the size, make a full GET request