mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
95 lines
3.9 KiB
JavaScript
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
|