Compare commits

..
13 Commits
18 changed files with 45 additions and 29 deletions
-1
View File
@@ -1 +0,0 @@
build/* linguist-generated
+1 -1
View File
@@ -8,7 +8,7 @@
<img src="./docs/public/mediabunny-logo.svg" height="180">
</div>
Mediabunny is a JavaScript library for reading, writing, and converting media files (like MP4 or WebM), 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 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.
[Documentation](https://mediabunny.dev) | [Examples](https://mediabunny.dev/examples) | [Sponsoring](#sponsoring) | [License](#license) | [Discord](https://discord.gg/hmpkyYuS4U)
+3 -2
View File
@@ -21,7 +21,7 @@
chunked: true,
chunkSize: 2**20
});
const outputFormat = new Mediabunny.MovOutputFormat();
const outputFormat = new Mediabunny.Mp4OutputFormat();
const button = document.createElement('button');
button.textContent = 'Cancel';
@@ -38,7 +38,8 @@
target
}),
audio: {
codec: 'pcm-f64'
codec: 'opus',
bitrate: 128000,
//numberOfChannels: 1,
//sampleRate: 4000
//discard: true
+4 -1
View File
@@ -11,11 +11,14 @@ export default withMermaid({
+ ' faster than anybunny else.',
cleanUrls: true,
head: [
['link', { rel: 'icon', type: 'image/svg+xml', href: '/mediabunny-logo.svg' }],
['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: '/mediabunny-og-image.png' }],
['meta', { property: 'og:locale', content: 'en-US' }],
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
],
themeConfig: {
logo: '/mediabunny-logo.svg',
+1 -1
View File
@@ -37,7 +37,7 @@ features:
target: _self
icon:
src: /mingcute--magic-3-line.svg
- title: Live recording
- title: Live recording & streaming
details: Record a video from live sources and stream it to a video element.
link: /examples/live-recording
target: _self
+7
View File
@@ -66,6 +66,13 @@ This library is the result of unifying these libraries into one, solving all the
Due to tree shaking, if you only need an MP4 or WebM muxer, this library's bundle size will still be very small.
### Migration
If you're coming from mp4-muxer or webm-muxer, you should migrate to Mediabunny. For that, refer to these guides:
- [Guide: Migrating from mp4-muxer to Mediabunny](https://github.com/Vanilagy/mp4-muxer/blob/main/MIGRATION-GUIDE.md)
- [Guide: Migrating from webm-muxer to Mediabunny](https://github.com/Vanilagy/webm-muxer/blob/main/MIGRATION-GUIDE.md)
## Technical overview
At its core, Mediabunny is a collection of multiplexers and demultiplexers, one of each for every container format. Demultiplexers stream data from *sources*, while multiplexers stream data to *targets*. Every demultiplexer is capable of extracting file metadata as well as compressed media data, while multiplexers write metadata and encoded media data into a new file.
+5 -4
View File
@@ -6,7 +6,7 @@ title: Mediabunny
hero:
name: Mediabunny
text: Complete media toolkit
tagline: A JavaScript library for reading, writing, and converting media files. Directly in the browser, and faster than anybunny else.
tagline: A JavaScript library for reading, writing, and converting video and audio files. Directly in the browser, and faster than anybunny else.
image:
src: /mediabunny-logo.svg
alt: Mediabunny logo
@@ -92,6 +92,7 @@ const sponsors = {
{ image: '/sponsors/diffusionstudio.png', name: 'Diffusion Studio', url: 'https://diffusion.studio/' },
],
individual: [
{ image: 'https://avatars.githubusercontent.com/u/84167135', name: 'Memenome', url: 'https://github.com/memenome' },
{ image: 'https://avatars.githubusercontent.com/u/30229596', name: 'Pablo Bonilla', url: 'https://github.com/devPablo' },
{ image: 'https://avatars.githubusercontent.com/u/58149663', name: 'H7GhosT', url: 'https://github.com/H7GhosT' },
],
@@ -123,7 +124,7 @@ npm install mediabunny
<div class="flex flex-col lg:flex-row lg:gap-20 lg:items-center">
<div class="flex-1 min-w-0">
<h1 class="inline-block" style="background: -webkit-linear-gradient(-30deg, #ff45ac, #ff78c2); -webkit-background-clip: text; color: transparent;">Read any media file, efficiently</h1>
<p class="text-lg">Mediabunny allows you efficiently read data from any media file, no matter the size: duration, resolution, rotation, tracks, codecs and other metadata, as well as raw or decoded media data from anywhere in the file. Load only what you need.</p>
<p class="text-lg">Mediabunny allows you efficiently read data from any video or audio file, no matter the size: duration, resolution, rotation, tracks, codecs and other metadata, as well as raw or decoded media data from anywhere in the file. Load only what you need.</p>
<a class="!no-underline inline-flex items-center gap-1.5" :no-icon="true" href="/guide/reading-media-files">
Docs
<span class="vpi-arrow-right" />
@@ -323,7 +324,7 @@ await conversion.execute();
<template v-if="sponsors.gold.length > 0">
<h3 class="!text-2xl">Gold sponsors</h3>
<div class="flex flex-wrap mt-1 justify-center gap-1">
<a v-for="sponsor in sponsors.gold" :href="sponsor.url" target="_blank" class="flex items-center p-2 rounded-full hover:bg-(--vp-c-gray-3) !text-[initial] !no-underline">
<a v-for="sponsor in sponsors.gold" :href="sponsor.url" target="_blank" class="flex items-center p-2 rounded-full hover:bg-(--vp-c-gray-3) !text-(--vp-c-text-1) !no-underline">
<img :src="sponsor.image" class="size-16 rounded-full">
<p class="!my-0 !font-medium px-3">{{ sponsor.name }}</p>
</a>
@@ -332,7 +333,7 @@ await conversion.execute();
<template v-if="sponsors.individual.length > 0">
<h4>Individual sponsors</h4>
<div class="flex flex-wrap mt-1 justify-center">
<a v-for="sponsor in sponsors.individual" :href="sponsor.url" target="_blank" class="flex gap-1 w-24 flex-col items-center p-2 rounded-xl hover:bg-(--vp-c-gray-3) !text-[initial] !no-underline">
<a v-for="sponsor in sponsors.individual" :href="sponsor.url" target="_blank" class="flex gap-1 w-24 flex-col items-center p-2 rounded-xl hover:bg-(--vp-c-gray-3) !text-(--vp-c-text-1) !no-underline">
<img :src="sponsor.image" class="size-8 rounded-full">
<p class="!my-0 !font-medium text-xs !leading-4">{{ sponsor.name }}</p>
</a>
@@ -113,6 +113,7 @@ const compressFile = async (file: File) => {
selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,audio/*';
fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0];
if (!file) {
+2 -2
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live recording example | Mediabunny</title>
<title>Live recording & streaming example | Mediabunny</title>
<script type="module" src="../base.ts"></script>
<script type="module" src="./live-recording.ts"></script>
<link rel="stylesheet" href="../base.css">
@@ -12,7 +12,7 @@
</head>
<body class="flex flex-col items-center py-10 bg-zinc-50 text-zinc-800 dark:bg-zinc-900 dark:text-zinc-200 px-2">
<h1 class="text-3xl font-bold text-orange-500 text-center">Live recording example</h1>
<h1 class="text-3xl font-bold text-orange-500 text-center">Live recording & streaming example</h1>
<p class="max-w-lg text-center">The live canvas state and your microphone input will be written into a fragmented MP4 file and live-streamed to a <code>&lt;video&gt;</code> element.</p>
<button id="toggle-button" class="rounded-lg bg-zinc-200 dark:bg-zinc-750 hover:bg-zinc-300 dark:hover:bg-zinc-700 px-5 py-2 mt-4">
+1
View File
@@ -544,6 +544,7 @@ const formatSeconds = (seconds: number) => {
selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,audio/*';
fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0];
if (!file) {
@@ -155,6 +155,7 @@ const shortDelay = () => {
selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,audio/*';
fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0];
if (!file) {
+1 -1
View File
@@ -38,7 +38,7 @@
<p id="error-element" class="text-red-500"></p>
<div class="w-full max-w-80 h-2 rounded-full bg-zinc-200 dark:bg-zinc-750 overflow-hidden" id="progress-bar-container" style="display: none;">
<div class="h-full bg-teal-500 w-0" id="progress-bar"></div>
<div class="h-full bg-pink-500 w-0" id="progress-bar"></div>
</div>
<p class="text-xs font-medium mt-1.5 tabular-nums" id="progress-text" style="display: none;"></p>
@@ -99,6 +99,7 @@ const generateThumbnails = async (file: File) => {
selectMediaButton.addEventListener('click', () => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'video/*,video/x-matroska,audio/*';
fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0];
if (!file) {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mediabunny",
"version": "0.1.0",
"version": "1.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mediabunny",
"version": "0.1.0",
"version": "1.0.2",
"license": "MPL-2.0",
"dependencies": {
"@types/dom-mediacapture-transform": "^0.1.11",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "mediabunny",
"author": "Vanilagy",
"version": "1.0.0",
"version": "1.0.2",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module",
"main": "./dist/mediabunny.js",
+6 -4
View File
@@ -416,8 +416,9 @@ class VideoEncoderWrapper {
const support = await VideoEncoder.isConfigSupported(encoderConfig);
if (!support.supported) {
throw new Error(
'This specific encoder configuration is not supported by this browser. Consider using another'
+ ' codec or changing your video parameters.',
`This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps,`
+ ` ${encoderConfig.width}x${encoderConfig.height}) is not supported by this browser. Consider`
+ ` using another codec or changing your video parameters.`,
);
}
@@ -988,8 +989,9 @@ class AudioEncoderWrapper {
const support = await AudioEncoder.isConfigSupported(encoderConfig);
if (!support.supported) {
throw new Error(
'This specific encoder configuration not supported by this browser. Consider using another'
+ ' codec or changing your audio parameters.',
`This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps,`
+ ` ${encoderConfig.numberOfChannels} channels, ${encoderConfig.sampleRate} Hz) is not`
+ ` supported by this browser. Consider using another codec or changing your audio parameters.`,
);
}
+7 -4
View File
@@ -134,7 +134,9 @@ export const writeBits = (bytes: Uint8Array, start: number, end: number, value:
};
export const toUint8Array = (source: AllowSharedBufferSource): Uint8Array => {
if (source instanceof ArrayBuffer) {
if (source instanceof Uint8Array) {
return source;
} else if (source instanceof ArrayBuffer) {
return new Uint8Array(source);
} else {
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
@@ -142,7 +144,9 @@ export const toUint8Array = (source: AllowSharedBufferSource): Uint8Array => {
};
export const toDataView = (source: AllowSharedBufferSource) => {
if (source instanceof ArrayBuffer) {
if (source instanceof DataView) {
return source;
} else if (source instanceof ArrayBuffer) {
return new DataView(source);
} else {
return new DataView(source.buffer, source.byteOffset, source.byteLength);
@@ -202,11 +206,10 @@ export const colorSpaceIsComplete = (
};
export const isAllowSharedBufferSource = (x: unknown) => {
// Quite a mouthful:
return (
x instanceof ArrayBuffer
|| (typeof SharedArrayBuffer !== 'undefined' && x instanceof SharedArrayBuffer)
|| (ArrayBuffer.isView(x) && !(x instanceof DataView))
|| ArrayBuffer.isView(x)
);
};
+1 -5
View File
@@ -890,7 +890,6 @@ export class AudioSample {
}
return new AudioData({
format: this.format,
sampleRate: this.sampleRate,
numberOfFrames: this.numberOfFrames,
@@ -900,11 +899,9 @@ export class AudioSample {
});
} else {
const data = new ArrayBuffer(this.allocationSize({ planeIndex: 0, format: this.format }));
this.copyTo(new DataView(data), { planeIndex: 0, format: this.format });
this.copyTo(data, { planeIndex: 0, format: this.format });
return new AudioData({
format: this.format,
sampleRate: this.sampleRate,
numberOfFrames: this.numberOfFrames,
@@ -916,7 +913,6 @@ export class AudioSample {
}
} else {
return new AudioData({
format: this.format,
sampleRate: this.sampleRate,
numberOfFrames: this.numberOfFrames,