Files
v10/dist/dev/media/dom/mse/duration.js
T

95 lines
3.9 KiB
JavaScript

import { hasPresentationDuration } from "../../types/index.js";
//#region src/media/dom/mse/duration.ts
/**
* Check if we have the basics to update MediaSource duration:
* a `mediaSource` and a `presentation` with a numeric duration.
*/
function canUpdateDuration(presentation, mediaSource) {
return !!(mediaSource && presentation && hasPresentationDuration(presentation));
}
function getBufferedEnd(buffers, isEndMatch) {
return [...buffers].reduce((endMatch, buffer) => {
const { buffered } = buffer;
if (!buffered.length) return endMatch;
const end = buffered.end(buffered.length - 1);
if (!endMatch) return end;
return isEndMatch(end, endMatch) ? end : endMatch;
}, void 0) ?? 0;
}
const isGreaterThan = (x, y) => x > y;
const isLessThan = (x, y) => x < y;
/**
* Get the maximum buffered end time across an iterable of SourceBuffers
* (typically `mediaSource.sourceBuffers`). Returns `0` when the collection is
* empty or no buffer has any buffered ranges.
*/
function getMaxBufferedEnd(buffers) {
return getBufferedEnd(buffers, isGreaterThan);
}
/**
* Get the reachable buffered end across an iterable of SourceBuffers (typically
* `mediaSource.sourceBuffers`): the `min` of each buffer's last buffered-range end
* — the furthest point every track can play to (the intersection end). Buffers with
* no buffered ranges are skipped. Returns `0` when the collection is empty or no
* buffer has any buffered ranges.
*
* Counterpart to {@link getMaxBufferedEnd}: `max` bounds the overall presentation
* end (e.g. for setting `duration`), `min` bounds where playback can actually reach
* when tracks end at slightly different times (e.g. skewed A/V near end-of-stream).
*/
function getMinBufferedEnd(buffers) {
return getBufferedEnd(buffers, isLessThan);
}
/**
* Check if the preconditions are met to *attempt* a `mediaSource.duration`
* write: a `mediaSource` is in scope and the presentation has a valid
* positive duration (or `Infinity` for live).
*
* Does **not** check `mediaSource.readyState` or `mediaSource.duration` —
* those are DOM properties the caller resolves at write time (e.g., by
* `await`ing `waitForMediaSourceOpen` and re-checking `readyState` after,
* and guarding on the existing `mediaSource.duration` for idempotency).
* Keeping these off the signal-driven predicate lets callers use this
* inside reactor state derivation without smuggling non-reactive DOM
* reads into `computed(...)`.
*
* `Infinity` is allowed — per the MSE spec, `mediaSource.duration = +Infinity`
* is how live playback signals an indefinite duration.
*/
function shouldUpdateDuration(presentation, mediaSource) {
if (!canUpdateDuration(presentation, mediaSource)) return false;
const duration = presentation.duration;
if (Number.isNaN(duration) || duration <= 0) return false;
return true;
}
/**
* Wait for all currently-updating SourceBuffers in `buffers` to finish, or
* until `signal` aborts — whichever fires first.
*
* The MSE spec forbids setting `MediaSource.duration` while any attached
* SourceBuffer has `updating === true`. This defers until all are idle.
* Listeners are registered with `{ signal }` so an abort tears them down
* up-front rather than leaving them dangling until the next `updateend`.
*/
function waitForSourceBuffersReady(buffers, signal) {
if (signal.aborted) return Promise.resolve();
const updating = [];
for (const buf of buffers) if (buf.updating) updating.push(buf);
if (updating.length === 0) return Promise.resolve();
return new Promise((resolve) => {
let remaining = updating.length;
const onUpdateEnd = () => {
remaining--;
if (remaining === 0) resolve();
};
for (const buf of updating) buf.addEventListener("updateend", onUpdateEnd, {
once: true,
signal
});
signal.addEventListener("abort", () => resolve(), { once: true });
});
}
//#endregion
export { canUpdateDuration, getBufferedEnd, getMaxBufferedEnd, getMinBufferedEnd, shouldUpdateDuration, waitForSourceBuffersReady };
//# sourceMappingURL=duration.js.map