import { ALL_FORMATS, BlobSource, BufferTarget, Conversion, Input, Mp4OutputFormat, Output, Quality, UrlSource, } from 'mediabunny'; import { registerAc3Decoder } from '@mediabunny/ac3'; import { registerDtsDecoder } from '@mediabunny/dts'; import { registerProresDecoder } from '@mediabunny/prores'; import SampleFileUrl from '../../docs/assets/big-buck-bunny-trimmed.mp4'; // Enable codecs that aren't natively supported by WebCodecs. registerAc3Decoder(); registerDtsDecoder(); registerProresDecoder(); (document.querySelector('#sample-file-download') as HTMLAnchorElement).href = SampleFileUrl; const selectMediaButton = document.querySelector('#select-file') as HTMLButtonElement; const loadUrlButton = document.querySelector('#load-url') as HTMLButtonElement; const fileNameElement = document.querySelector('#file-name') as HTMLParagraphElement; const horizontalRule = document.querySelector('hr') as HTMLHRElement; const progressBarContainer = document.querySelector('#progress-bar-container') as HTMLDivElement; const progressBar = document.querySelector('#progress-bar') as HTMLDivElement; const speedometer = document.querySelector('#speedometer') as HTMLParagraphElement; const videoElement = document.querySelector('video') as HTMLVideoElement; const compressionFacts = document.querySelector('#compression-facts') as HTMLParagraphElement; const errorElement = document.querySelector('#error-element') as HTMLParagraphElement; let currentConversion: Conversion | null = null; let currentIntervalId = -1; const compressFile = async (resource: File | string) => { clearInterval(currentIntervalId); await currentConversion?.cancel(); fileNameElement.textContent = resource instanceof File ? resource.name : resource; horizontalRule.style.display = ''; progressBarContainer.style.display = ''; speedometer.style.display = ''; speedometer.textContent = 'Speed: -'; videoElement.style.display = 'none'; videoElement.src = ''; errorElement.textContent = ''; try { // Create a new input from the resource const source = resource instanceof File ? new BlobSource(resource) : new UrlSource(resource); const input = new Input({ source, formats: ALL_FORMATS, // Accept all formats }); const fileSize = await source.getSize(); // Define the output file const output = new Output({ target: new BufferTarget(), format: new Mp4OutputFormat(), }); // Initialize the conversion process currentConversion = await Conversion.init({ input, output, tracks: 'primary', // Keep only one track per type video: { width: 320, // Height will be deduced automatically to retain aspect ratio quality: new Quality('very-low'), }, audio: { codec: 'opus', quality: new Quality('very-low'), }, }); if (!currentConversion.isValid) { console.info(currentConversion.discardedTracks); throw new Error('Conversion is invalid and cannot be executed; see the console for more.'); } // Keep track of progress let progress = 0; let processedTime = 0; let startTime: number | null = null; currentConversion.onProgress = (newProgress, newProcessedTime) => { progress = newProgress; processedTime = newProcessedTime; startTime ??= performance.now(); }; const updateProgress = () => { progressBar.style.width = `${progress * 100}%`; if (startTime !== null) { const now = performance.now(); const elapsedSeconds = (now - startTime) / 1000; const factor = processedTime / elapsedSeconds; speedometer.textContent = `Speed: ~${factor.toPrecision(3)}x real time`; } }; // Update the progress indicator regularly currentIntervalId = window.setInterval(updateProgress, 1000 / 60); // Start the conversion process await currentConversion.execute(); clearInterval(currentIntervalId); updateProgress(); // Display the final media file videoElement.style.display = ''; videoElement.src = URL.createObjectURL(new Blob([output.target.buffer!], { type: output.format.mimeType })); void videoElement.play(); compressionFacts.style.display = ''; compressionFacts.textContent = `${(output.target.buffer!.byteLength / fileSize * 100).toPrecision(3)}% of original size`; } catch (error) { console.error(error); await currentConversion?.cancel(); errorElement.textContent = String(error); clearInterval(currentIntervalId); progressBarContainer.style.display = 'none'; speedometer.style.display = 'none'; compressionFacts.style.display = 'none'; videoElement.style.display = 'none'; } }; /** === FILE SELECTION LOGIC === */ selectMediaButton.addEventListener('click', () => { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'video/*,video/x-matroska,video/mp2t,.mkv,.ts,audio/*,audio/aac,.aac'; fileInput.addEventListener('change', () => { const file = fileInput.files?.[0]; if (!file) { return; } void compressFile(file); }); fileInput.click(); }); loadUrlButton.addEventListener('click', () => { const url = prompt( 'Please enter a URL of a media file. Note that it must be HTTPS and support cross-origin requests, so have the' + ' right CORS headers set.', 'https://mediabunny.dev/big-buck-bunny.mp4', ); if (!url) { return; } void compressFile(url); }); document.addEventListener('dragover', (event) => { event.preventDefault(); event.dataTransfer!.dropEffect = 'copy'; }); document.addEventListener('drop', (event) => { event.preventDefault(); const files = event.dataTransfer?.files; const file = files && files.length > 0 ? files[0] : undefined; if (file) { void compressFile(file); } });