mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-05 23:03:54 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b382d3e57 | ||
|
|
f78fc697cd | ||
|
|
39df195ada | ||
|
|
326e03cfc9 | ||
|
|
23aef27154 | ||
|
|
9daab77bb5 | ||
|
|
5a6e3eebda | ||
|
|
c4fb325329 | ||
|
|
1071bf9d49 | ||
|
|
a8fa2e4cec | ||
|
|
12f14b1f66 | ||
|
|
d4d93a7007 | ||
|
|
f44cb26d76 |
@@ -1 +0,0 @@
|
||||
build/* linguist-generated
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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><video></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">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Generated
+2
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user