Mobile-first full-screen reels for Reddit feeds: swipe, one stream with sound, Reddit video and RedGifs, native voting.
A mobile-first userscript (Violentmonkey / Tampermonkey; Firefox Android, Kiwi, Safari/Orion, desktop) that turns any Reddit feed into a full-screen, swipeable reel: one post per screen, one stream with sound, Reddit video and RedGifs, native voting.
Tap the Reels button on a feed. The reel opens over Reddit; the phone's Back button (or ✕ / Esc) closes it and leaves you on the post you were watching.
The reel is its own full-screen view (shadow DOM, own CSS), not a restyle of Reddit's page. Restyling Reddit's markup breaks whenever the markup differs per device, which is what happened in v2. Reddit's page stays underneath, hidden, as the data source.
| Piece | How |
|---|---|
| Posts | Read from Reddit's rendered <shreddit-post> attributes (title, author, score, media). |
| More posts | Same HTML endpoint Reddit's feed uses (faceplate-partial[slot=load-after], e.g. /svc/shreddit/community-more-posts/…?after=). New pages are inserted into Reddit's real feed, so they are live posts. No .json API, no rate-limit traps. |
| Reddit video | Direct packaged-media.redd.it mp4 (has audio) when Reddit provides one (re-read when the slide opens, since Reddit fills it in late); otherwise the HLS stream through the full hls.js build (Reddit keeps audio in a separate playlist, which the light build can't play). Native HLS is only a last resort: Chrome claims support but fails Reddit's streams. If a source fails, the next one is tried. |
| Reddit "gifs" | Served as a silent mp4 (preview.redd.it/…gif?format=mp4), played directly. (Before 3.2 they went to the HLS player and failed, e.g. all of r/gifs.) |
| RedGifs | RedGifs API → mp4 → blob. Requests go through GM_xmlhttpRequest because Reddit's CSP blocks redgifs hosts and RedGifs blocks Reddit referrers. If that fails, the slide falls back to RedGifs' own player iframe. |
| YouTube / Streamable | Their embed player, mounted only while the slide is on screen. |
| Audio | One shared <video> moved into the active slide. Only one stream can exist, so no audio bleed. Once your first tap has played it, mobile browsers keep allowing sound on later slides. |
| Voting | Clicks Reddit's own vote buttons inside the post, so auth and CSRF stay Reddit's. Logged out → a "log in" toast. |
| Text posts | Full-height story: the title is sized by length, the body keeps Reddit's formatting (paragraphs, lists, quotes, links, spoilers) through an allow-list sanitizer. Long bodies fade out; Read more (or tapping the text) opens a scrollable reader sheet. Back or Esc closes it. The body never scrolls inside the slide, so a swipe always goes to the next post. |
| App nags | Mobile Reddit points post, user and subreddit links at applink.reddit.com, an App Link that launches the Reddit app. They are rewritten to www.reddit.com (tracking params dropped), and the header Open App button is hidden. |
| Comments | Opens Reddit's post page (new Reddit) in a new tab, so the reel keeps its place. |
| Memory | Only the active slide and its neighbours hold images/iframes; far slides are emptied. |
| Landscape | On a phone held sideways, video slides hide the close button, subreddit, title, rail and seek bar (opacity only, so swiping stays smooth). A tap brings them back for 3 s; they stay while paused. Rotating keeps the same slide in view. |
| Swiping | The slide switches only after the scroll settles, and live UI (seek bar, spinner, pulses) sits in a fixed layer above the track. Changing layout inside a scroll-snap track mid-swipe makes browsers snap back, which felt like "swipe twice". |
| Gesture / key | Action |
|---|---|
Swipe up / down, J K, ↓ ↑ |
Next / previous |
| Tap | Play / pause (first tap after a blocked autoplay turns sound on; a ▶ shows when the browser blocked autoplay entirely) |
| Double tap | Upvote |
| Tap a long text post / Read more | Reader sheet (Back / Esc / ✕ closes it) |
| Rotate the phone (video) | Immersive: only the video; tap shows the controls for 3 s, the next tap plays/pauses |
| Drag the bottom bar | Seek |
← → |
Gallery image, or seek ±5 s |
| Space | Play / pause |
M |
Mute |
C |
Captions (Reddit videos that have them) |
F / rail button |
Fit ↔ fill screen |
Back, ✕, Esc |
Close |
Mute is remembered. Vertical videos fill the screen by default; others are letterboxed over a blurred backdrop.
dist/reddit-reels.user.js and click Install.https://www.reddit.com/ and tap the orange button.hls.js (full build) is loaded via @require from jsDelivr. @connect covers api.redgifs.com / media.redgifs.com; allow it when the manager asks.
src/
├── index.ts # Entry
├── app.ts # FAB on feed routes, open/close, Back button, comments in a new tab
├── core/route.ts # Feed-route matcher + client-side navigation watcher
├── feed/
│ ├── extract.ts # <shreddit-post> → Post (video/redgifs/embed/gallery/image/text/link)
│ ├── sanitize.ts # Allow-list copy of a text post's markdown HTML
│ ├── source.ts # Posts in the page + next pages via Reddit's load-after partial
│ ├── vote.ts # Native vote buttons
│ └── types.ts
├── media/
│ ├── player.ts # The one shared <video>: mp4 → HLS fallback chain, RedGifs blob, mute, captions
│ └── redgifs.ts # Token, gif lookup, blob download (GM_xmlhttpRequest)
├── reel/
│ ├── reel.ts # Overlay: snap track + fixed HUD, settle-based activation, gestures, keys, voting
│ ├── slide.ts # Slide shell + mount/unmount of heavy content
│ ├── reel.css # Shadow-root styles (mobile first, phone column on desktop)
│ └── icons.ts
├── page/declutter.ts # App links → www.reddit.com, hide "Open App"
├── ui/fab.ts # Floating Reels button (own shadow root)
└── utils.ts
tests/
├── fixtures/feed.html # Real Reddit feed markup (video+mp4, video HLS-only, gallery, image, text, link)
├── fixtures/extras.html # Real "gif" post (r/gifs) and formatted text post (r/tifu)
├── fixtures/server.ts # Serves them + build + a fake load-after endpoint
├── unit/ # Extractor, feed paging, routes, RedGifs/API retry, source choice
└── reel.spec.ts # Playwright (Pixel 7): open, swipe, unmount, paging, Back, comments tab, keys
bun install
bun test tests/unit
bun run build # dist/reddit-reels.user.js (unminified, @license MIT)
bun run test:e2e # needs the build; PW_CHROMIUM=/path/to/chromium to override the browser
The Playwright Chromium has no H.264 decoder, so e2e tests check sources, slides and lifecycle, not actual decoding. Playback with audio was verified separately in Google Chrome against live mobile Reddit (mp4 and HLS slides).
<shreddit-post> attributes, feed/extract.ts is the one place to update.