demo · v138
Sync Removal Demo
Chrome 138 removes asynchronous range removal from Media Source Extensions. remove(start, end) must be called synchronously (not from a Promise callback or async function while MSE is updating). Use the timeline below to practice the correct synchronous removal pattern and see the error the old async approach would cause.
Checking MediaSource API…
Simulated buffer (click a segment to select it)
0s5s10s15s20s25s30s
Selected range: none
Start (s)
End (s)
Click a segment or set a range, then click remove…
What changed in Chrome 138
Old pattern (breaks in Chrome 138)
// ❌ async — throws InvalidStateError
sourceBuffer.addEventListener('updateend', async () => {
await somethingAsync();
// ← now we're in a microtask / Promise callback
sourceBuffer.remove(0, 5); // THROWS in Chrome 138
});
New pattern (Chrome 138)
// ✓ synchronous — works correctly
sourceBuffer.addEventListener('updateend', () => {
// No awaits — we're in sync event handler
if (needsRemoval) {
sourceBuffer.remove(startTime, endTime);
// ^^^^^^^^^^^^^^^^^^
// Called synchronously inside the event handler
}
});
// MediaSource setup
const ms = new MediaSource();
video.src = URL.createObjectURL(ms);
ms.addEventListener('sourceopen', () => {
const sb = ms.addSourceBuffer('video/mp4; codecs="avc1.42E01E"');
// ✓ Correct: remove() called synchronously in updateend
sb.addEventListener('updateend', () => {
if (shouldPruneBuffer) {
// Direct synchronous call — no async/await before this
sb.remove(0, currentTime - 30); // keep 30s lookback
}
});
// ❌ Wrong (Chrome 138+): async gap before remove()
// sb.addEventListener('updateend', async () => {
// await checkSomething(); // ← introduces async gap
// sb.remove(0, 5); // InvalidStateError!
// });
});