(params?: {
resolveStartSeconds?: (element: Element) => number;
resolveDurationSeconds?: (element: HTMLVideoElement | HTMLAudioElement) => number | null;
shouldIncludeElement?: (element: HTMLVideoElement | HTMLAudioElement) => boolean;
})
| 27 | }; |
| 28 | |
| 29 | export function refreshRuntimeMediaCache(params?: { |
| 30 | resolveStartSeconds?: (element: Element) => number; |
| 31 | resolveDurationSeconds?: (element: HTMLVideoElement | HTMLAudioElement) => number | null; |
| 32 | shouldIncludeElement?: (element: HTMLVideoElement | HTMLAudioElement) => boolean; |
| 33 | }): { |
| 34 | timedMediaEls: Array<HTMLVideoElement | HTMLAudioElement>; |
| 35 | mediaClips: RuntimeMediaClip[]; |
| 36 | videoClips: RuntimeMediaClip[]; |
| 37 | maxMediaEnd: number; |
| 38 | } { |
| 39 | const mediaEls = Array.from(document.querySelectorAll("video, audio")) as Array< |
| 40 | HTMLVideoElement | HTMLAudioElement |
| 41 | >; |
| 42 | const timedMediaEls = params?.shouldIncludeElement |
| 43 | ? mediaEls.filter((el) => params.shouldIncludeElement?.(el)) |
| 44 | : mediaEls.filter((el) => el.hasAttribute("data-start")); |
| 45 | const mediaClips: RuntimeMediaClip[] = []; |
| 46 | const videoClips: RuntimeMediaClip[] = []; |
| 47 | let maxMediaEnd = 0; |
| 48 | for (const el of timedMediaEls) { |
| 49 | const start = params?.resolveStartSeconds |
| 50 | ? params.resolveStartSeconds(el) |
| 51 | : Number.parseFloat(el.dataset.start ?? "0"); |
| 52 | if (!Number.isFinite(start)) continue; |
| 53 | const mediaStart = |
| 54 | Number.parseFloat(el.dataset.playbackStart ?? el.dataset.mediaStart ?? "0") || 0; |
| 55 | const playbackRate = readElementPlaybackRate(el); |
| 56 | const loop = el.loop; |
| 57 | const sourceDuration = Number.isFinite(el.duration) && el.duration > 0 ? el.duration : null; |
| 58 | let duration = |
| 59 | params?.resolveDurationSeconds?.(el) ?? Number.parseFloat(el.dataset.duration ?? ""); |
| 60 | if ((!Number.isFinite(duration) || duration <= 0) && sourceDuration != null) { |
| 61 | // Effective duration accounts for playback rate: |
| 62 | // at 0.5x, a 10s source plays for 20s on the timeline |
| 63 | duration = Math.max(0, (sourceDuration - mediaStart) / playbackRate); |
| 64 | } |
| 65 | const end = |
| 66 | Number.isFinite(duration) && duration > 0 ? start + duration : Number.POSITIVE_INFINITY; |
| 67 | const volumeRaw = Number.parseFloat(el.dataset.volume ?? ""); |
| 68 | const clip: RuntimeMediaClip = { |
| 69 | el, |
| 70 | start, |
| 71 | mediaStart, |
| 72 | duration: Number.isFinite(duration) && duration > 0 ? duration : Number.POSITIVE_INFINITY, |
| 73 | end, |
| 74 | volume: Number.isFinite(volumeRaw) ? volumeRaw : null, |
| 75 | playbackRate, |
| 76 | loop, |
| 77 | sourceDuration, |
| 78 | }; |
| 79 | mediaClips.push(clip); |
| 80 | if (el.tagName === "VIDEO") videoClips.push(clip); |
| 81 | if (Number.isFinite(end)) maxMediaEnd = Math.max(maxMediaEnd, end); |
| 82 | } |
| 83 | return { timedMediaEls, mediaClips, videoClips, maxMediaEnd }; |
| 84 | } |
| 85 | |
| 86 | // Per-element timeline→media offset from the previous tick. Used to tell a |
no test coverage detected