carousel/vanilla HLS MP4
The same strip without React, on attach + set
examples/carousel/vanilla/main.ts
import { createPlayer, type Player, playFullscreen, preload } from "@uln/hls";
import { type Film, fetchFilms, saveProgress } from "../../films";
/** The carousel without React: the same calls, wired by hand. */
const strip = document.getElementById("strip") as HTMLUListElement;
const films: Film[] = [];
const clips: Player[] = [];
const centered = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
// The first callback reports every card, a neighbour peeking at the edge included.
if (entry.intersectionRatio < 0.6) continue;
const index = [...strip.children].indexOf(entry.target);
for (const [i, clip] of clips.entries()) {
if (i === index) clip.play();
else clip.pause();
}
preload([films[index + 1]?.clip?.url, films[index - 1]?.clip?.url, films[index + 2]?.clip?.url]);
// Endless: three from the end, fetch the next page.
if (index === films.length - 3) void loadMore();
}
},
{ root: strip, threshold: 0.6 },
);
function addCard(f: Film) {
const card = document.createElement("li");
card.className = "card";
card.innerHTML = `<button type="button"><video></video><h2></h2></button>`;
(card.querySelector("h2") as HTMLElement).textContent = f.title;
strip.append(card);
const clip = createPlayer();
const filmTime = () => (f.clip ? f.clip.start + clip.state.currentTime : 0);
if (f.clip) {
const video = card.querySelector("video") as HTMLVideoElement;
clip.attach({ video, src: f.clip.url, poster: f.poster });
clip.set({ muted: true, loop: true, onPlay: () => preload(f.hls, { start: filmTime() }) });
}
clips.push(clip);
card.querySelector("button")?.addEventListener("click", () => {
playFullscreen(f.hls, { start: filmTime(), onClose: (time) => saveProgress(f.id, time) });
});
centered.observe(card);
}
async function loadMore() {
const more = await fetchFilms(films.length / 10);
films.push(...more);
for (const f of more) addCard(f);
}
void loadMore();