Files
mediabunny/dev/convert.html
T
b05cdbe7e0 fix: two long-running HLS transcode issues (#355)
* fix: release targets from Output._targets on finalize

Long-running HLS transcodes leak memory. Every finalized BufferTarget
stays in _targets until the outer Output closes, pinning its buffer.
Writer.finalize() already does this cleanup for writer-based flows;
extend it to buffer-finalize paths via the public 'finalized' event.

* fix: ReadOrchestrator LRU eviction picks only drained workers

assert(pendingSlices.length === 0) fires under heavy concurrent reads
(e.g. multi-rendition HLS decode from BlobSource). LRU filter only
checked !running; workers with queued slices could be evicted.
Add pendingSlices.length === 0 to the filter.

* fix: export AppendOnlyStreamTarget from index

Missing from the re-export; public docs import it by name.

* fix: join HLS init segment path with root + playlist path

Init path went through _getTarget bare; segments got joined with rootPath + playlist.path. Playlist-relative URI then can't resolve when the playlist lives in a subdirectory.

* Fix targets not being cleaned up, fix paused workers with remaining pending slices, modify doc block, fixed isRoot not being changed on proxied requests

---------

Co-authored-by: Vanilagy <[email protected]>
2026-04-23 14:37:56 +02:00

200 lines
4.7 KiB
HTML

<!DOCTYPE html>
<script src="../dist/bundles/mediabunny.cjs"></script>
<script src="../packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js"></script>
<script src="../packages/ac3/dist/bundles/mediabunny-ac3.js"></script>
<script type="module">
//MediabunnyMp3Encoder.registerMp3Encoder();
MediabunnyAc3.registerAc3Encoder();
const fileInput = document.createElement('input');
fileInput.type = 'file';
document.body.append(fileInput);
const progressElement = document.createElement('progress');
progressElement.max = 1;
document.body.append(progressElement);
fileInput.addEventListener('change', async () => {
const target = new Mediabunny.BufferTarget() ?? new Mediabunny.StreamTarget(new WritableStream({
write: console.log
}), {
chunked: true,
chunkSize: 2**20
});
const outputFormat = new Mediabunny.HlsOutputFormat({
segmentFormat: new Mediabunny.MpegTsOutputFormat(),
});
const p = document.createElement('p');
p.textContent = 'Capturing...';
document.body.append(p);
/*
const button = document.createElement('button');
button.textContent = 'Cancel';
button.onclick = () => conversion.cancel();
document.body.append(button);
*/
const yo = document.createElement('canvas');
yo.width = 512;
yo.height = 512;
const context = yo.getContext('2d', { alpha: false });
context.fillStyle = 'red';
context.fillRect(100, 100, 300, 300);
const blob = await new Promise(resolve => yo.toBlob(resolve, 'image/jpeg', 0.92));
const blobData = new Uint8Array(await blob.arrayBuffer());
const mh = new Blob([blobData], { type: 'image/jpeg' });
console.log(URL.createObjectURL(mh))
const output = new Mediabunny.Output({
format: outputFormat,
target: new Mediabunny.PathedTarget('master.m3u8', ({ path }) => new Mediabunny.BufferTarget()),
});
let input;
const tracks = [];
let start = 0;
if (false) {
input = new Mediabunny.Input({
entryPath: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',
source: ({ path }) => new Mediabunny.UrlSource(path),
formats: Mediabunny.ALL_FORMATS,
});
/*
const videoTrack = await input.getPrimaryVideoTrack();
const audioTrack = await input.getPrimaryAudioTrack();
if (videoTrack) tracks.push(videoTrack);
if (audioTrack) tracks.push(audioTrack);
start = Math.min(...await Promise.all([
videoTrack.computeDuration({ skipLiveWait: true }),
audioTrack.computeDuration({ skipLiveWait: true }),
]));
*/
} else {
const file = fileInput.files[0];
const source = new Mediabunny.BlobSource(file);
input = new Mediabunny.Input({
formats: Mediabunny.ALL_FORMATS,
source
});
}
let ctx = null;
let conversion = await Mediabunny.Conversion.init({
input,
output,
audio: (track, n) => [{
codec: 'aac',
}, {
codec: 'aac',
}],
/*
video: {
discard: true,
bitrate: Mediabunny.QUALITY_VERY_HIGH,
//bitrate: Mediabunny.QUALITY_VERY_HIGH
codec: 'av1',
//width: 800,
//fit: 'fill',
//rotate: 90,
},
audio: {
bitrate: Mediabunny.QUALITY_HIGH,
codec: 'aac',
},
*/
/*
video: {
codec: 'avc',
},
audio: {
codec: 'mp3',
bitrate: 320000
},
*/
video: [
{ height: 1080 },
{ height: 720 },
{ height: 480 },
{ height: 360 },
{ height: 240 },
],
tags: {} ?? {
title: 'Bigggy',
artist: 'Buck Bunny',
images: [{
data: blobData,
kind: 'coverFront',
mimeType: 'image/jpeg'
}],
trackNumber: 4,
tracksTotal: 10,
discNumber: 5,
discsTotal: 8,
lyrics: "There's no way\nThat it's not going there",
raw: {
'AIGC': 'Mediabunny epic own encoder'
}
},
trim: {
//end: 5,
//start,
//end: start + 5,
////start: 0,
//end: 2
},
});
//console.log(conversion);
let progress = 0;
conversion.onProgress = newProgress => progress = newProgress;
function updateProgress() {
progressElement.value = progress;
if (progress === 1) {
return;
}
setTimeout(updateProgress, 1000/60);
}
updateProgress();
console.time();
await conversion.execute();
console.timeEnd()
console.log("Done", target.buffer);
input.dispose()
const video = document.createElement('video');
video.src = URL.createObjectURL(new Blob([target.buffer], { type: outputFormat.mimeType }));
video.controls = true;
document.body.append(video);
video.play();
//download(new Blob([target.buffer]), 'converted' + outputFormat.fileExtension);
function download(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
conversion.onProgress = null;
conversion = null;
input = null;
}, { once: true });
</script>