Pocket YouTube: Streaming YouTube to a PSP over a USB Cable · PocketJS
← BlogOne search-to-playback journey on a real PSP. Every frame here is the device's own framebuffer, captured over the same USB cable the video streams through — the input is a replay tape, the screenshots ride the DevTools channel.<br>The Sony PSP has WiFi. It is 802.11b, it tops out around 100 KB/s on a good day, and its idea of TLS predates the certificates, the ciphers, and frankly the internet that YouTube lives on. No amount of software on the device will make that radio speak to a 2026 CDN.
But look at the machine sitting next to it. During development, a PSP is tethered to a laptop anyway — PSPLINK mounts a directory of your machine as the device's host0: drive over USB 2.0. That cable moves about a megabyte per second of file I/O. A megabyte per second is not much by any modern standard, and it is also, if you are careful, exactly enough to stream video.
So that became the project: YouTube on the PSP, where the network is a USB cable. Search with an on-screen keyboard, browse real results — thumbnails, Chinese titles, view counts — pick one, and watch it, with sound, with pause and seek, on 2004 hardware. A Mac companion process owns everything the PSP cannot do (DNS, TLS, yt-dlp, H.264), and the handheld owns everything it can: a 60 Hz UI, a texture, and an audio ring. If you are new here, the device side is PocketJS — our runtime that runs real Solid JSX on the PSP — and this app is one more entry in its growing family of proofs, merged as #113.
This post is the whole story: a stream container you can ls, a 256-color video plane, an audio thread with no allocator, and the three bugs — a GPU race, a leaked hardware channel, a build system that lied — that only a real device would ever have shown us.
Split the app at the network boundary
The design rule is the same one every project in this family obeys: the device never parses the world, it only consumes what a build step — here, a live one — has already chewed. Pocket Figma froze a 22 MB design file into tile pyramids; Pocket YouTube does the same thing to a video, except the "build step" is a Mac process running while you watch.
Mac — host/serve.ts<br>owns DNS · TLS · YouTube · H.264
yt-dlp — search · resolve itag 22/18<br>one progressive URL, video+audio muxed
ffmpeg ×2<br>-re · 720p → 512×128<br>audio → 22.05 kHz s16
quantizer<br>median cut · 256 colors<br>Floyd–Steinberg dither
pocket-svc/youtube/ — the wire is a directory<br>out.jsonl / in.jsonl — command mailbox<br>thumbs/*.img — result rows, pre-rendered<br>media/play-N.pkst — THE STREAM, one file<br>every message carries the request id it answers
USB 2.0<br>usbhostfs<br>host0:/
PSP — the PocketJS app<br>owns 60 Hz UI · texture · audio ring
PocketJS app — Solid JSX<br>search · rows · player HUD · system keyboard
videoTick() — bounded pump<br>≤26 KB of file I/O per 60 Hz tick, main thread
video plane<br>512×128 CLUT8<br>one GE texture
audio thread<br>44.1 kHz native<br>2× upsample<br>no sockets · no decoder · no allocation surprises<br>the device reads three kinds of files, and that is all<br>the Mac owns the network and the pixels; the PSP owns presentation<br>kill the Mac process mid-video and the app shows a frozen frame, not a crash — restart it and the session resumes<br>The mailbox deserves one sentence of respect. It is two append-only JSON-lines files — the device writes {"t":"search","id":2,"q":"psp"} into out.jsonl, the Mac appends {"t":"results","id":2,...} to in.jsonl — and it generalizes the transport our time-travel debugger already proved out. Every reply echoes the request id, so the app routes responses without ordering assumptions; every bulk payload (a thumbnail row, the stream itself) travels as a side file named in the message. Request/response over tail -f, effectively. It is not glamorous. It is inspectable with cat, testable with a canned driver, and survives either side restarting — the device detects a truncated mailbox and rewinds its read offset.
Search results are worth a look before we get to video, because they solve a problem PocketJS cannot solve on-device: arbitrary text. The PSP build bakes a font atlas of exactly the glyphs the app's source mentions — search results can name any Unicode codepoint in existence. So the Mac renders each result as one 512×64 CLUT8 image : thumbnail, duration badge, title in any script, channel, view count, all typeset with opentype.js and shipped as a side file. The device shows a texture; it never meets a glyph it doesn't know.
Host-rendered rows on the device: titles in any script the PSP's atlas could never bake (CJK included), a duration chip on each thumbnail, rounded corners masked into the pixels — because the GE's scissor is rectangular and cannot round anything.<br>A video stream you can ls
There is no socket to stream over, so the stream is a file with the shape of a ring buffer — a format we call .pkst. The Mac writes it in place, forever; the PSP polls its 96-byte header and chases the tail....