diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml
new file mode 100644
index 0000000..44dafce
--- /dev/null
+++ b/.github/workflows/build-check.yml
@@ -0,0 +1,28 @@
+name: Build check
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+jobs:
+ build-check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+
+ - name: Set up Node.js
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ node-version: 22
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ # Building the docs also means building the core library
+ - name: Build everything
+ run: npm run docs:build
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 4fc52dd..3f4fbc7 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -27,7 +27,4 @@ jobs:
run: npm run check
- name: Run ESLint
- run: npm run lint
-
- - name: Run build
- run: npm run build
+ run: npm run lint
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b2b2155..744c4a1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -19,13 +19,11 @@ jobs:
with:
fetch-depth: 0
- - name: Merge main into release branch
- run: |
- git config user.name "github-actions[bot]"
- git config user.email "github-actions[bot]@users.noreply.github.com"
- git checkout release
- git merge origin/main --no-ff -m "Merge main into release for tag ${{ github.event.release.tag_name }}"
- git push origin release
+ - name: Enforce stable releases target main
+ if: ${{ !github.event.release.prerelease && github.event.release.target_commitish != 'main' }}
+ uses: actions/github-script@ffc2c79a5b2490bd33e0a41c1de74b877714d736 # v3.2.0
+ with:
+ script: "core.setFailed('Non-prerelease releases must target the main branch (got: ${{ github.event.release.target_commitish }}).')"
- name: Set up Node.js
uses: actions/setup-node@v4
@@ -35,7 +33,7 @@ jobs:
registry-url: https://registry.npmjs.org
- name: Update npm
- run: npm install -g npm@latest
+ run: npm install -g npm@11.11.0
- name: Get package.json version
id: package-json-version
@@ -53,16 +51,59 @@ jobs:
- name: Run tests
run: xvfb-run npm test
- - name: Run build
- run: npm run build
+ # Building the docs also builds the core library
+ - name: Build everything
+ run: npm run docs:build
- name: Upload build artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: gh release upload ${{ github.event.release.tag_name }} dist/bundles/mediabunny.cjs dist/bundles/mediabunny.min.cjs dist/bundles/mediabunny.mjs dist/bundles/mediabunny.min.mjs dist/mediabunny.d.ts packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.mjs packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.mjs packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
-
- - name: Publish Mediabunny to npm
- run: npm publish --access public
+ run: >
+ gh release upload ${{ github.event.release.tag_name }}
+ dist/bundles/mediabunny.cjs
+ dist/bundles/mediabunny.min.cjs
+ dist/bundles/mediabunny.mjs
+ dist/bundles/mediabunny.min.mjs
+ dist/mediabunny.d.ts
+ packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js
+ packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.js
+ packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.mjs
+ packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.mjs
+ packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
+ packages/ac3/dist/bundles/mediabunny-ac3.js
+ packages/ac3/dist/bundles/mediabunny-ac3.min.js
+ packages/ac3/dist/bundles/mediabunny-ac3.mjs
+ packages/ac3/dist/bundles/mediabunny-ac3.min.mjs
+ packages/ac3/dist/mediabunny-ac3.d.ts
+ packages/aac-encoder/dist/bundles/mediabunny-aac-encoder.js
+ packages/aac-encoder/dist/bundles/mediabunny-aac-encoder.min.js
+ packages/aac-encoder/dist/bundles/mediabunny-aac-encoder.mjs
+ packages/aac-encoder/dist/bundles/mediabunny-aac-encoder.min.mjs
+ packages/aac-encoder/dist/mediabunny-aac-encoder.d.ts
+ packages/flac-encoder/dist/bundles/mediabunny-flac-encoder.js
+ packages/flac-encoder/dist/bundles/mediabunny-flac-encoder.min.js
+ packages/flac-encoder/dist/bundles/mediabunny-flac-encoder.mjs
+ packages/flac-encoder/dist/bundles/mediabunny-flac-encoder.min.mjs
+ packages/flac-encoder/dist/mediabunny-flac-encoder.d.ts
+ packages/prores/dist/bundles/mediabunny-prores.js
+ packages/prores/dist/bundles/mediabunny-prores.min.js
+ packages/prores/dist/bundles/mediabunny-prores.mjs
+ packages/prores/dist/bundles/mediabunny-prores.min.mjs
+ packages/prores/dist/mediabunny-prores.d.ts
+ packages/server/dist/bundles/mediabunny-server.cjs
+ packages/server/dist/bundles/mediabunny-server.min.cjs
+ packages/server/dist/bundles/mediabunny-server.mjs
+ packages/server/dist/bundles/mediabunny-server.min.mjs
+ packages/server/dist/mediabunny-server.d.ts
- name: Publish workspace packages to npm
- run: npm publish --access public --workspaces
+ run: npm publish --access public --workspaces ${{ github.event.release.prerelease && '--tag beta' || '' }}
+
+ - name: Merge main into release branch
+ if: ${{ !github.event.release.prerelease }}
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git checkout release
+ git merge origin/main --no-ff -m "Merge main into release for tag ${{ github.event.release.tag_name }}"
+ git push origin release
diff --git a/.gitignore b/.gitignore
index a359208..30c66a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,9 @@ node_modules
/docs/api
*.tsbuildinfo
-packages/mp3-encoder/dist
\ No newline at end of file
+packages/mp3-encoder/dist
+packages/ac3/dist
+packages/aac-encoder/dist
+packages/flac-encoder/dist
+packages/server/dist
+packages/prores/dist
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3b881ed..a8e60ea 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,5 +3,6 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
- "typescript.tsdk": "node_modules/typescript/lib"
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "js/ts.tsdk.path": "node_modules/typescript/lib"
}
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..7773a16
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,6 @@
+- Prefer functions declared using const, not using the function keyword
+- Code style is tab indent with semicolons
+- Mediabunny core code is contained in src/, extensions are in packages/*/, website is in docs/
+- Tests: Prefer fewer, longer test files over many small ones. Test files should be named after the general category of thing that is being tested, not after any individual single test.
+- Avoid ifs without a {} block. So no if (cond) return;, always do if (cond) { return; }
+- `type` instead of `interface` for object types
diff --git a/README.md b/README.md
index 738f4fb..08a0196 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-Mediabunny is a JavaScript library for reading, writing, and converting media files (like MP4, WebM, MP3), directly in the browser. It aims to be a complete toolkit for high-performance media operations on the web. It's written from scratch in pure TypeScript, has zero dependencies, is very performant, and is extremely tree-shakable, meaning you only include what you use. You can think of it a bit like [FFmpeg](https://ffmpeg.org/), but built from the ground up for the web.
+Mediabunny is a JavaScript library for reading, writing, and converting media (like MP4, WebM, MP3, HLS), directly in the browser. It aims to be a complete toolkit for high-performance media operations on the web. It's written from scratch in pure TypeScript, has zero dependencies, is very performant, and is extremely tree-shakable, meaning you only include what you use. You can think of it a bit like [FFmpeg](https://ffmpeg.org/), but built from the ground up for the web.
[Documentation](https://mediabunny.dev) | [Examples](https://mediabunny.dev/examples) | [Sponsoring](#sponsoring) | [License](#license) | [Discord](https://discord.gg/hmpkyYuS4U)
@@ -34,15 +34,19 @@ Mediabunny is a JavaScript library for reading, writing, and converting media fi
+
+
+
+
+
+
+
+
### Silver sponsors
-
-
@@ -51,6 +55,10 @@ Mediabunny is a JavaScript library for reading, writing, and converting media fi
### Bronze sponsors
[Sponsor Mediabunny's development](https://github.com/sponsors/Vanilagy)
@@ -70,14 +82,14 @@ Mediabunny is a JavaScript library for reading, writing, and converting media fi
Core features include:
-- **Wide format support**: Read and write MP4, MOV, WebM, MKV, WAVE, MP3, Ogg, ADTS, FLAC
-- **Built-in encoding & decoding**: Supports 25+ video, audio, and subtitle codecs, hardware-accelerated using the WebCodecs API
-- **High precision**: Fine-grained, microsecond-accurate reading and writing operations
-- **Conversion API**: Easy-to-use API with features such as transmuxing, transcoding, resizing, rotation, cropping, resampling, trimming, and more
-- **Streaming I/O**: Handle reading & writing files of any size with memory-efficient streaming
-- **Tree-shakable**: Only bundle what you use (as small as 5 kB gzipped)
-- **Zero dependencies**: Implemented in highly performant TypeScript
-- **Cross-platform**: Works in browsers and Node.js
+- Wide format support: Read _and_ write MP4, MOV, WebM, MKV, HLS, WAVE, MP3, Ogg, ADTS, FLAC, MPEG-TS
+- Built-in encoding & decoding: Supports 25+ video, audio, and subtitle codecs, hardware-accelerated using the WebCodecs API
+- High precision: Fine-grained, microsecond-accurate reading and writing operations
+- Conversion API: Easy-to-use API with features such as transmuxing, transcoding, resizing, rotation, cropping, resampling, trimming, and more
+- Streaming I/O: Handle reading & writing files of any size with memory-efficient streaming
+- Cross-platform: Works in all browsers as well as in Node, Bun, and Deno using [`@mediabunny/server`](https://github.com/Vanilagy/mediabunny/blob/main/packages/server/README.md)
+- Tree-shakable: Only bundle what you use (as small as 5 kB gzipped)
+- Zero dependencies: Implemented in highly performant TypeScript
[See full feature list](https://mediabunny.dev/guide/introduction#features)
@@ -103,16 +115,23 @@ Requires any JavaScript environment that can run ECMAScript 2021 or later. Media
```js
import { Input, ALL_FORMATS, BlobSource } from 'mediabunny';
+// Reading from disk
const input = new Input({
- source: new BlobSource(file), // Reading from disk
+ source: new BlobSource(file),
formats: ALL_FORMATS,
});
const duration = await input.computeDuration(); // in seconds
const videoTrack = await input.getPrimaryVideoTrack();
const audioTrack = await input.getPrimaryAudioTrack();
-const { displayWidth, displayHeight, rotation } = videoTrack;
-const { sampleRate, numberOfChannels } = audioTrack;
+
+const displayWidth = await videoTrack.getDisplayWidth();
+const displayHeight = await videoTrack.getDisplayHeight();
+const rotation = await videoTrack.getRotation();
+
+const sampleRate = await audioTrack.getSampleRate();
+const numberOfChannels = await audioTrack.getNumberOfChannels();
+
const { title, artist, album } = await input.getMetadataTags();
```
diff --git a/build.sh b/build.sh
deleted file mode 100755
index e1d5e58..0000000
--- a/build.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/bash
-set -e
-
-# This script must be executed via `npm run build`
-
-# Clear the stuff from last build
-rm -rf dist
-rm -rf packages/mp3-encoder/dist
-
-# Ensure license headers on all source files
-tsx scripts/ensure-license-headers.ts
-
-# Type check & generate .js and .d.ts files
-tsc -p src
-tsc -p packages/mp3-encoder/src
-
-# So that the resulting files use valid ESM imports with file extension. This only runs for the core Mediabunny as only
-# it ships the individual files to npm (for tree shaking, because it's large)
-npm run fix-build-import-paths
-
-# Creates bundles for all packages
-tsx scripts/bundle.ts
-
-# Declaration file rollup and checks
-api-extractor run
-api-extractor run -c packages/mp3-encoder/api-extractor.json
-
-# Checks that all symbols are documented
-tsx scripts/check-docblocks.ts dist/mediabunny.d.ts
-tsx scripts/check-docblocks.ts packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
-
-# Checks that API docs are generatable
-npm run docs:generate -- --dry
-
-# Appends stuff to the declaration files to register the global variables these libraries expose
-echo 'export as namespace Mediabunny;' >> dist/mediabunny.d.ts
-echo 'export as namespace MediabunnyMp3Encoder;' >> packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
\ No newline at end of file
diff --git a/dev/convert.html b/dev/convert.html
index df27399..9b5117e 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -2,9 +2,13 @@
+
+
\ No newline at end of file
diff --git a/dev/demux.html b/dev/demux.html
index 5309287..9ce0b0e 100644
--- a/dev/demux.html
+++ b/dev/demux.html
@@ -1,8 +1,11 @@
+
\ No newline at end of file
+
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 82b2751..836b76e 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -1,13 +1,19 @@
+/* eslint-disable @stylistic/max-len */
import { withMermaid } from 'vitepress-plugin-mermaid';
import footnote from 'markdown-it-footnote';
import tailwindcss from '@tailwindcss/vite';
import llmstxt from 'vitepress-plugin-llms';
import { HeadConfig } from 'vitepress';
-// @ts-expect-error This file gets generated once docs:generate is run
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore This file gets generated once docs:generate is run
import apiRoutes from '../api/index.json';
+import m3u8Grammar from './m3u8-grammar.json' with { type: 'json' };
+import fs from 'node:fs/promises';
+import path from 'node:path';
const DESCRIPTION = 'A JavaScript library for reading, writing, and converting media files. Directly in the browser,'
+ ' and faster than anybunny else.';
+const ORIGIN = 'https://mediabunny.dev';
// https://vitepress.dev/reference/site-config
export default withMermaid({
@@ -15,21 +21,31 @@ export default withMermaid({
description: DESCRIPTION,
cleanUrls: true,
sitemap: {
- hostname: 'https://mediabunny.dev',
+ hostname: ORIGIN,
+ transformItems: async (items) => {
+ const entries = await fs.readdir('./examples');
+ for (const entry of entries) {
+ const isDirectory = await fs.stat(path.join('./examples', entry)).then(stat => stat.isDirectory());
+ if (isDirectory) {
+ items.push({
+ url: `/examples/${entry}/`, // With trailing slash
+ });
+ }
+ }
+
+ return items;
+ },
},
+ // lastUpdated: true,
head: [
['link', { rel: 'icon', type: 'image/png', href: '/mediabunny-logo.png' }],
['link', { rel: 'icon', type: 'image/svg+xml', href: '/mediabunny-logo.svg' }],
- ['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:site_name', content: 'Mediabunny' }],
- ['meta', { property: 'og:url', content: 'https://mediabunny.dev/' }],
- ['meta', { property: 'og:image', content: 'https://mediabunny.dev/mediabunny-og-image.png' }],
+ ['meta', { property: 'og:image', content: `${ORIGIN}/mediabunny-og-image.png` }],
['meta', { property: 'og:locale', content: 'en-US' }],
- ['meta', { property: 'og:description', content: DESCRIPTION }],
- ['meta', { name: 'twitter:image', content: 'https://mediabunny.dev/mediabunny-og-image.png' }],
+ ['meta', { name: 'twitter:image', content: `${ORIGIN}/mediabunny-og-image.png` }],
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
['meta', { name: 'twitter:site', content: '@vanilagy' }],
- ['meta', { name: 'twitter:description', content: DESCRIPTION }],
],
themeConfig: {
logo: '/mediabunny-logo.svg',
@@ -37,11 +53,18 @@ export default withMermaid({
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Guide', link: '/guide/introduction', activeMatch: '/guide' },
- { text: 'API', link: '/api/', activeMatch: '/api' },
+ { text: 'API', link: '/api/', activeMatch: '/api/' }, // Trailing slash because it's index.html from there
{ text: 'LLMs', link: '/llms', activeMatch: '/llms' },
{ text: 'Examples', link: '/examples', activeMatch: '/examples' },
+ { text: 'Blog', link: '/blog', activeMatch: '/blog' },
{ text: 'Sponsors', link: '/#sponsors', activeMatch: '/#sponsors' },
- { text: 'License', link: 'https://github.com/Vanilagy/mediabunny#license' },
+ { text: 'License', link: 'https://github.com/Vanilagy/mediabunny#license', rel: 'noopener' },
+ {
+ text: 'More',
+ items: [
+ { text: 'Codec Registry', link: '/codec-registry/overview' },
+ ],
+ },
],
sidebar: {
@@ -83,16 +106,63 @@ export default withMermaid({
{ text: 'Supported formats & codecs', link: '/guide/supported-formats-and-codecs' },
],
},
+ {
+ text: 'HLS',
+ items: [
+ { text: 'Reading HLS', link: '/guide/reading-hls' },
+ { text: 'Writing HLS', link: '/guide/writing-hls' },
+ ],
+ },
{
text: 'Extensions',
items: [
+ { text: 'server', link: '/guide/extensions/server' },
{ text: 'mp3-encoder', link: '/guide/extensions/mp3-encoder' },
+ { text: 'aac-encoder', link: '/guide/extensions/aac-encoder' },
+ { text: 'ac3', link: '/guide/extensions/ac3' },
+ { text: 'flac-encoder', link: '/guide/extensions/flac-encoder' },
+ { text: 'prores', link: '/guide/extensions/prores' },
+ ],
+ },
+ ],
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
+ '/api': apiRoutes as any,
+
+ '/codec-registry': [
+ {
+ text: 'Codec registry',
+ items: [
+ { text: 'Overview', link: '/codec-registry/overview' },
+ ],
+ },
+ {
+ text: 'Video',
+ items: [
+ { text: 'AVC (H.264)', link: '/codec-registry/avc' },
+ { text: 'HEVC (H.265)', link: '/codec-registry/hevc' },
+ { text: 'VP8', link: '/codec-registry/vp8' },
+ { text: 'VP9', link: '/codec-registry/vp9' },
+ { text: 'AV1', link: '/codec-registry/av1' },
+ { text: 'ProRes', link: '/codec-registry/prores' },
+ ],
+ },
+ {
+ text: 'Audio',
+ items: [
+ { text: 'AAC', link: '/codec-registry/aac' },
+ { text: 'Opus', link: '/codec-registry/opus' },
+ { text: 'MP3', link: '/codec-registry/mp3' },
+ { text: 'Vorbis', link: '/codec-registry/vorbis' },
+ { text: 'FLAC', link: '/codec-registry/flac' },
+ { text: 'AC-3', link: '/codec-registry/ac3' },
+ { text: 'E-AC-3', link: '/codec-registry/eac3' },
+ { text: 'Linear PCM', link: '/codec-registry/pcm' },
+ { text: 'μ-law PCM', link: '/codec-registry/ulaw' },
+ { text: 'A-law PCM', link: '/codec-registry/alaw' },
],
},
],
- // Fails in CI without this
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- '/api': apiRoutes as any[],
},
socialLinks: [
@@ -112,12 +182,15 @@ export default withMermaid({
footer: {
message: 'Released under the Mozilla Public License 2.0.',
- copyright: 'Copyright © 2025-present Vanilagy',
+ copyright: `Copyright © ${new Date().getFullYear()}-present Vanilagy`,
},
},
markdown: {
math: true,
theme: { light: 'github-light', dark: 'github-dark-dimmed' },
+ shikiSetup: (shiki) => {
+ shiki.loadLanguageSync(m3u8Grammar);
+ },
config(md) {
md.use(footnote);
},
@@ -129,6 +202,8 @@ export default withMermaid({
llmstxt({
ignoreFiles: [
'api/*',
+ 'examples.md',
+ 'llms.md',
],
}),
],
@@ -140,9 +215,95 @@ export default withMermaid({
title += ' | Mediabunny';
}
+ const canonicalUrl = `${ORIGIN}/${pageData.relativePath}`
+ .replace(/index\.md$/, '')
+ .replace(/\.md$/, '');
+ const isBlogPost = canonicalUrl.includes('/blog/');
+
+ const breadcrumbs: object[] = [];
+
+ if (canonicalUrl.includes('/guide/')) {
+ breadcrumbs.push({
+ '@type': 'ListItem',
+ 'position': 1,
+ 'name': 'Guide',
+ 'item': `${ORIGIN}/guide`,
+ }, {
+ '@type': 'ListItem',
+ 'position': 2,
+ 'name': pageData.title,
+ });
+ }
+
+ if (canonicalUrl.includes('/api/')) {
+ breadcrumbs.push({
+ '@type': 'ListItem',
+ 'position': 1,
+ 'name': 'API docs',
+ 'item': `${ORIGIN}/api/`,
+ }, {
+ '@type': 'ListItem',
+ 'position': 2,
+ 'name': pageData.title,
+ });
+ }
+
+ if (canonicalUrl.includes('/codec-registry/')) {
+ breadcrumbs.push({
+ '@type': 'ListItem',
+ 'position': 1,
+ 'name': 'Codec registry',
+ 'item': `${ORIGIN}/codec-registry/overview`,
+ }, {
+ '@type': 'ListItem',
+ 'position': 2,
+ 'name': pageData.title,
+ });
+ }
+
((pageData.frontmatter['head'] ??= []) as HeadConfig[]).push(
+ ['meta', { property: 'og:type', content: isBlogPost ? 'article' : 'website' }],
['meta', { property: 'og:title', content: title }],
- ['meta', { property: 'twitter:title', content: title }],
+ ['meta', { property: 'og:description', content: pageData.description || DESCRIPTION }],
+ ['meta', { property: 'og:url', content: canonicalUrl }],
+ ['meta', { name: 'twitter:title', content: title }],
+ ['meta', { name: 'twitter:description', content: pageData.description || DESCRIPTION }],
+ ['link', { rel: 'canonical', href: canonicalUrl }],
);
+
+ if (isBlogPost) {
+ ((pageData.frontmatter['head'] ??= []) as HeadConfig[]).push(
+ ['meta', { property: 'article:published_time', content: String(pageData.frontmatter['publishedOnIso']) }],
+ ['meta', { property: 'article:author', content: String(pageData.frontmatter['author']) }],
+ );
+
+ breadcrumbs.push({
+ '@type': 'ListItem',
+ 'position': 1,
+ 'name': 'Blog posts',
+ 'item': `${ORIGIN}/blog`,
+ }, {
+ '@type': 'ListItem',
+ 'position': 2,
+ 'name': pageData.title,
+ });
+ }
+
+ if (breadcrumbs.length > 0) {
+ ((pageData.frontmatter['head'] ??= []) as HeadConfig[]).push(
+ ['script', { type: 'application/ld+json' }, JSON.stringify({
+ '@context': 'https://schema.org',
+ '@type': 'BreadcrumbList',
+ 'itemListElement': breadcrumbs,
+ })],
+ );
+ }
+ },
+ buildEnd: async () => {
+ const files = await fs.readdir('./docs/api');
+
+ for (const file of files) {
+ await fs.copyFile('./docs/api/' + file, './dist-docs/api/' + file);
+ }
},
});
diff --git a/docs/.vitepress/m3u8-grammar.json b/docs/.vitepress/m3u8-grammar.json
new file mode 100644
index 0000000..6a672f0
--- /dev/null
+++ b/docs/.vitepress/m3u8-grammar.json
@@ -0,0 +1,113 @@
+{
+ "name": "m3u8",
+ "scopeName": "source.m3u8",
+ "fileTypes": ["m3u8"],
+ "patterns": [
+ {
+ "match": "^#EXTM3U$",
+ "name": "keyword.control.m3u8"
+ },
+ {
+ "begin": "^(#)(EXTINF)(:)",
+ "beginCaptures": {
+ "1": { "name": "punctuation.definition.comment.m3u8" },
+ "2": { "name": "keyword.control.extinf.m3u8" },
+ "3": { "name": "punctuation.separator.key-value.m3u8" }
+ },
+ "end": "$",
+ "patterns": [
+ {
+ "match": "\\b\\d+(?:\\.\\d+)?\\b",
+ "name": "string.unquoted.m3u8"
+ },
+ {
+ "match": ",",
+ "name": "punctuation.separator.sequence.m3u8"
+ },
+ {
+ "match": "[^,\\n]+",
+ "name": "string.unquoted.title.m3u8"
+ }
+ ]
+ },
+ {
+ "begin": "^(#)(EXT-X-[A-Z0-9-]+|EXT-[A-Z0-9-]+)(:)",
+ "beginCaptures": {
+ "1": { "name": "punctuation.definition.comment.m3u8" },
+ "2": { "name": "keyword.control.tag.m3u8" },
+ "3": { "name": "punctuation.separator.key-value.m3u8" }
+ },
+ "end": "$",
+ "patterns": [
+ {
+ "include": "#attribute-list"
+ },
+ {
+ "match": "\\b(?:YES|NO)\\b",
+ "name": "string.unquoted.m3u8"
+ },
+ {
+ "match": "\\b\\d+(?:\\.\\d+)?\\b",
+ "name": "string.unquoted.m3u8"
+ },
+ {
+ "match": "[^,\\n]+",
+ "name": "string.unquoted.m3u8"
+ }
+ ]
+ },
+ {
+ "match": "^(#)(EXT-X-[A-Z0-9-]+|EXT-[A-Z0-9-]+)$",
+ "captures": {
+ "1": { "name": "punctuation.definition.comment.m3u8" },
+ "2": { "name": "keyword.control.tag.m3u8" }
+ }
+ },
+ {
+ "match": "^[^#\\s][^\\n]*$",
+ "name": "source.m3u8.uri"
+ },
+ {
+ "match": "#.*$",
+ "name": "comment.line.number-sign.m3u8"
+ }
+ ],
+ "repository": {
+ "attribute-list": {
+ "patterns": [
+ {
+ "match": "\\b[A-Z0-9-]+(?==)",
+ "name": "variable.parameter.m3u8"
+ },
+ {
+ "match": "=",
+ "name": "keyword.operator.assignment.m3u8"
+ },
+ {
+ "match": ",",
+ "name": "punctuation.separator.sequence.m3u8"
+ },
+ {
+ "match": "\"[^\"]*\"",
+ "name": "string.quoted.double.m3u8"
+ },
+ {
+ "match": "\\b\\d+x\\d+\\b",
+ "name": "string.unquoted.m3u8"
+ },
+ {
+ "match": "\\b\\d+(?:\\.\\d+)?\\b",
+ "name": "string.unquoted.m3u8"
+ },
+ {
+ "match": "\\b(?:YES|NO)\\b",
+ "name": "string.unquoted.m3u8"
+ },
+ {
+ "match": "\\b[A-Z0-9_-]+\\b",
+ "name": "string.unquoted.m3u8"
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/api-config.json b/docs/api-config.json
index 1cd9433..b56656e 100644
--- a/docs/api-config.json
+++ b/docs/api-config.json
@@ -1,6 +1,7 @@
{
"heading": "Mediabunny API reference",
"intro": "Here you can find detailed documentation for all classes, functions, constants and types exposed by Mediabunny's public API.",
+ "description": "Detailed documentation for all classes, functions, constants and types exposed by Mediabunny's public API.",
"Samples": "Raw, unencoded chunks of media data, such as video frames or sections of audio.",
"Packets": "Chunks of encoded media data.",
@@ -16,8 +17,15 @@
"Metadata tags": "Descriptive metadata tags attached to media files.",
"Codecs": "Codecs understood by Mediabunny.",
"Encoding": "Encoder configuration and encodability checks.",
+ "Decoding": "Decoder configuration and decodability checks.",
"Custom coders": "API for adding custom encoders and decoders.",
+ "Logging": "Control over what Mediabunny logs to the console.",
"Miscellaneous": "Whatever's left.",
- "@mediabunny/mp3-encoder": "Adds MP3 encoder support to Mediabunny."
+ "@mediabunny/server": "Adds full video/audio decoder and encoder support to Mediabunny running in server-side environments such as Node, Bun, or Deno.",
+ "@mediabunny/mp3-encoder": "Adds MP3 encoder support to Mediabunny.",
+ "@mediabunny/ac3": "Adds AC-3/E-AC-3 decoder and encoder support to Mediabunny.",
+ "@mediabunny/aac-encoder": "Polyfills AAC encoder support to Mediabunny.",
+ "@mediabunny/flac-encoder": "Adds FLAC encoder support to Mediabunny.",
+ "@mediabunny/prores": "Adds Apple ProRes decoder support to Mediabunny."
}
diff --git a/docs/assets/codec-soup.svg b/docs/assets/codec-soup.svg
index 32b746b..22969f2 100644
--- a/docs/assets/codec-soup.svg
+++ b/docs/assets/codec-soup.svg
@@ -1,46 +1,56 @@
-