The player above is the same series as the claw machine write up, playing through the same pipeline, with none of the same interface. Every pixel of it is this site's.
What headless actually means here
A skinless player usually means you inherit somebody's controls and fight the CSS until they look like yours. This one means a <video> element in a shadow root, display block, no background, no object-fit, no sizing opinion, and a comment in the source explaining that shipping a look you then have to undo is not headless.
What you do not inherit is the hard part, which stays inside: it takes a series id, asks the session route what is in it, mints a session per episode, applies the DRM, stitches and reports the ads, and advances the queue when one ends. I get told about all of it and have to draw none of it.
el.addEventListener("otnet-series", onSeries); // the whole series arrives
el.addEventListener("otnet-series-item", onItem); // which episode is on now
el.addEventListener("otnet-ready", onReady); // the media is resolved
el.addEventListener("otnet-time", onTime); // { currentTime, duration }
// And back the other way:
el.playSeriesItem(index);
el.play();
el.pause();
el.seekTo(seconds);That is the entire contract. Four events in, four calls out, nothing imported from the player bundle. The episodes arrive carrying their own artwork, duration, season title and episode number, which is what makes an interface like this one possible at all: without the frames there is nothing to orbit.
Why an orbit, and what it cost
A playlist is a list because a list is easy to read and nobody gets hurt. But a build series is not a list, it is a sequence you move along, and I wanted the thing you are watching to be the centre with the rest of the run arranged around it by how near they are.
The episodes ahead of you sweep down the right, the ones behind you down the left, and the further an episode is in the running order the further out it drifts. Click one and it flies into the middle, tucking behind the video as it goes, and plays.
Thirty tiles in orbit is not a design, it is a screensaver.
So the ring holds a window of four either side, nine episodes at a time, and the rest wait at zero opacity in the middle. They stay mounted rather than coming and going, which keeps the ring's rhythm steady and means a tile that comes round does not start its life mid bob.
The ring breathes rather than spins. A full rotation looks wonderful for about four seconds and then sweeps a tile behind the video you are watching, where nobody can click it. So it rocks a couple of degrees over twenty four seconds, each tile bobs on its own offset, and the whole thing is CSS transforms on wrappers. No JavaScript runs per frame.
The unglamorous half
Anyone who has asked their machine for less motion gets a ring that holds still, through a prefers-reduced-motion block that stops both animations. The fly to centre survives, shortened, because that one is feedback rather than decoration.
Below 640px the orbit is gone entirely and the episodes are a strip under the video. An orbit on a phone is a pile. Every tile is a real button with a real accessible name, so the whole thing drives from the keyboard and reads out loud as episode four of thirty rather than as an image.
And the video element stays on screen no matter what the skin is doing, because the player draws the ad countdown over it. A viewer in an ad break already knows it is an advert. What they do not know is how long it lasts, and that is not mine to take away.
