docs: document inline media embeds feature

- README: new bullet for inline media embeds (video/audio/post players + facade).
- LOTUS_FEATURES: 'Inline Media Embeds' section (kinds table, facade, provider
  notes, sandbox, setting, CSP, the fixed YouTube-thumbnail web bug).
- LOTUS_TODO: awaiting-verification row (16 providers) + deferred providers
  (Bandcamp / on.soundcloud / Vimeo event) under the feature backlog + the iframe
  onError gap.
- LOTUS_TESTING: section Q (facade, TikTok, post self-resize + close, new
  Bluesky/Loom/Kick + toggle + cap).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 12:13:01 -04:00
parent 07b0c410ab
commit 7ad948e26c
4 changed files with 108 additions and 0 deletions
+59
View File
@@ -767,6 +767,65 @@ Redacted events display "This message has been deleted" along with the redaction
Generic (non-domain-specific) cards display a Google S2 favicon. Empty or unparseable preview responses are suppressed entirely rather than showing a blank card.
### Inline Media Embeds
Media links play/render **in place** instead of opening a browser tab. A pure
resolver, `parseMediaEmbed(url, host)` in `src/app/utils/videoEmbed.ts`, maps a
URL to `{ provider, kind, embedUrl }`; `MediaEmbedCard` / `TikTokEmbedCard` /
`TwitterCard` in `UrlPreviewCard.tsx` render it. Four render `kind`s:
| kind | shape | providers |
| ----------- | ------------------------ | ----------------------------------------------------------------- |
| `landscape` | 16:9 video player | YouTube, Vimeo, Dailymotion, Streamable, Twitch, Loom, Kick (live) |
| `portrait` | 9:16 video player | YouTube Shorts, TikTok |
| `audio` | fixed-height audio player | Spotify, SoundCloud, Apple Music, Tidal |
| `rich` | self-resizing post embed | X/Twitter, Instagram, Reddit, Bluesky |
**Privacy-friendly facade.** The tile first shows the homeserver's cached
`og:image` thumbnail + a play button; the third-party `<iframe>` is only mounted
on click, so nothing hits Google/Meta/etc. until the user opts in. A **Close**
button collapses a playing embed back to the facade, and video players carry a
Fullscreen control. Cookie-less/DNT variants are used where offered
(`youtube-nocookie.com`, Vimeo `dnt=1`).
**Provider notes.**
- **TikTok** — short "copy-link" URLs (`vm.tiktok.com`, `tiktok.com/t/…`) carry no
video id and the homeserver preview is bot-walled, so `TikTokEmbedCard`
resolves the id client-side via TikTok's CORS-enabled **oEmbed** API on click
(`AbortController`-guarded), then plays the `player/v1` embed.
- **Reddit / Instagram / Bluesky / X** — post embeds self-size via `postMessage`;
`useIframeAutoHeight` listens scoped to each provider's origin **and** our own
iframe, parsing each provider's height shape (Instagram `MEASURE`, Reddit
`resize.embed`, Twitter `twttr.private.resize`). `redd.it` short links resolve to
the subreddit-less `embed.reddit.com/comments/{id}/` route.
- **Vimeo** unlisted-video privacy hashes (`vimeo.com/{id}/{hash}`) and
channel/group/album forms are parsed; **YouTube** handles `/watch`, `youtu.be`,
`/embed`, `/live`, `/shorts`, and `m.`/`music.youtube.com`.
**Defense-in-depth.** Every embed iframe carries a `sandbox` that omits
`allow-top-navigation` (so a compromised embed can't redirect the whole app —
phishing guard) on top of the CSP `frame-src` allowlist. Previews are also capped
at 6 per message.
**Setting.** `inlineMediaEmbeds` (Settings → General → "Inline Media Players",
default **on**). Off → media links fall back to plain link tiles.
**Latent web bug fixed along the way.** YouTube thumbnails now come from the
homeserver `og:image` instead of `img.youtube.com` — which was silently broken on
the web build (nginx `img-src` has no YouTube host) — removing a pre-click Google
request as a bonus.
**CSP.** Desktop Tauri `frame-src` (`cinny-desktop` `tauri.conf.json`) and the web
nginx `frame-src` allowlist enumerate every embed host (youtube-nocookie,
player.vimeo, geo.dailymotion, streamable, player/clips.twitch, open.spotify,
w.soundcloud, embed.music.apple, embed.tidal, www.tiktok + connect-src for its
oEmbed, platform.twitter, www.instagram, embed.reddit, embed.bsky.app, www.loom,
player.kick).
**Files:** `src/app/utils/videoEmbed.ts` (resolver + parsers, unit-tested),
`src/app/components/url-preview/{UrlPreviewCard,UrlPreview.css}.tsx`.
### Poll Creation
- `PollCreator.tsx` creates stable `m.poll.start` events