{"$schema":"https://ejfox.com/schema/page-twin@1.json","kind":"project","url":"https://ejfox.com/projects/tv-station","json_url":"https://ejfox.com/projects/tv-station.json","generator":"ejfox.com/json-twin@1","data":{"cacheVersion":"2026-05-13-gear-cards","html":"<p class=\"\">On-demand broke the one good thing about cable: everybody watching the same thing at the same second — no pausing, nobody skipping ahead, no algorithm picking for you. So I built my own channel. Everyone who tunes in to <a href=\"https://tv.tools.ejfox.com\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://tv.tools.ejfox.com\">tv.tools.ejfox.com</a> lands on the exact same frame, because the playhead is computed deterministically from a fixed epoch and the running sum of every video's duration. No database, no per-client state to keep in sync — just a single-file Deno app on smallweb. There's a live \"N other viewers\" counter too, so you can feel whether it's just you or a whole room.</p>\n<p class=\"\">The room I built it for is a little Discord community — people learning things together. Everyone can tune in and out and comment on what's happening at the same time, even though underneath it's just a playlist looping.</p>\n<figure role=\"figure\" aria-label=\"The station mid-loop — everyone watching the same frame of the 224-hour broadcast\" class=\"\"><video class=\"img-full my-8 rounded-sm\" autoplay loop muted playsinline preload=\"metadata\" poster=\"https://res.cloudinary.com/ejf/video/upload/so_0,f_jpg,q_auto,w_1280/projects/tv-station/demo.jpg\" src=\"https://res.cloudinary.com/ejf/video/upload/q_auto/projects/tv-station/demo.mp4\" aria-label=\"The station mid-loop — everyone watching the same frame of the 224-hour broadcast\"></video><figcaption class=\"\">The station mid-loop — everyone watching the same frame of the 224-hour broadcast</figcaption></figure>\n<p class=\"\">What's actually playing is my own YouTube \"Watch Later\" list — 507 saved links, enriched down to the 475 that are still alive, about 224 hours of runtime looping around. So tuning in is really watching over my shoulder through the backlog I never got around to. It's a whole cable channel, and it's also just... my saved videos.</p>\n<figure role=\"figure\" aria-label=\"The TV station landing — tuned to the channel\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/tv-station/landing.png\" alt=\"The TV station landing — tuned to the channel\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/tv-station/landing.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/tv-station/landing.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/tv-station/landing.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/tv-station/landing.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"The TV station landing — tuned to the channel\" width=\"1440\" height=\"900\" style=\"--splay-rot:-1.97deg;\" data-dimensions=\"1440×900\" class=\"img-full my-8 rounded-sm img-splay w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">The TV station landing — tuned to the channel</figcaption>\n</figure>\n<figure role=\"figure\" aria-label=\"tv.tools.ejfox.com live\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/tv-station/web-0.png\" alt=\"tv.tools.ejfox.com live\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/tv-station/web-0.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/tv-station/web-0.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/tv-station/web-0.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/tv-station/web-0.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"tv.tools.ejfox.com live\" width=\"1440\" height=\"900\" style=\"--splay-rot:0.6deg;\" data-dimensions=\"1440×900\" class=\"img-full my-8 rounded-sm img-splay w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">tv.tools.ejfox.com live</figcaption>\n</figure>\n<p class=\"\">I also wrote up a Pi 5 kiosk recipe so it runs full-screen on the wall TV — an actual television, tuned to my channel.</p>\n<p class=\"\">The whole thing is <a href=\"https://github.com/ejfox/tv-station\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://github.com/ejfox/tv-station\">open source<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" class=\"\"></path></svg></a>, and the point is that it's <em class=\"\">yours</em>, not mine. It's one ~140-line Deno file plus a small Python recipe that eats a Watch Later export, an Obsidian or Notion vault, or a plain CSV and builds the playlist for you. Point it at your own backlog, drop it on smallweb behind any reverse proxy, and you've got your own public-access channel in an afternoon.</p>","title":"TV Station","metadata":{"title":"TV Station","date":"2026-05-01T04:00:00.000Z","category":"Art","featured":false,"url":"https://tv.tools.ejfox.com","tech":["TypeScript","Deno","Smallweb"],"state":"deployed","ai-involvement":"ai-assisted","tags":["web","experiment"],"words":318,"images":3,"imageDetails":{"total":3,"cloudinary":3,"withDimensions":0},"links":2,"codeBlocks":0,"headers":{},"toc":[],"type":"post"}},"_links":{"self":"https://ejfox.com/projects/tv-station.json","html":"https://ejfox.com/projects/tv-station","index":"/projects.json"}}