6.2 KiB
Sharing plan: links + player landing + Motif export
This document captures the implementation plan for Share links (Copy Link + Share to X), a minimal player landing page, and the next step: sharing generated Motif variants via exported artifacts (MIDI and/or audio).
Goals
- Shareable outcome: users can share something they heard (original MIDI preview, or a generated Motif variant).
- One-click-to-hear: shared links load quickly and are ready to play (but never true autoplay; iOS requires a user gesture).
- Lightweight: keep the landing page minimal and stable.
- Safe: shared URLs must not turn the backend into an open proxy.
MVP constraints (important)
- iOS/Safari blocks audio until a user gesture. The landing can preload, but playback needs a tap.
- X/Twitter shares are links + preview cards. No reliable in-post WebAudio playback.
- Search results can change over time, so index-based sharing is brittle.
UX spec
A) Main app: Share controls
Placement: near the “Selected MIDI”/result context (where users decide something is share-worthy).
Controls:
- Copy link: copies a permalink to clipboard + shows a small “Copied” toast.
- Share to X: opens the tweet composer with prefilled text + URL.
X intent URL format:
https://twitter.com/intent/tweet?text=<encodedText>&url=<encodedShareUrl>
Suggested default tweet text:
Listening to “{title}” in MOTIF — try it
B) Share landing: /play
A minimal landing page that:
- reads parameters from the URL
- fetches the MIDI (or exported artifact)
- enables Play/Stop + volume
- shows iOS “Enable Audio” CTA if required
- includes a primary CTA: Generate your own → links back to the main app
URL design (heart of the plan)
v1: share the original MIDI preview (recommended MVP)
Use the MIDI URL as the stable identifier.
Landing URL:
/play?u=<encodedMidiUrl>&title=<encodedTitle>&song=<encodedQuery>
Notes:
uis required for reproducibility.titleis display-only (optional).songsupports the CTA back to the main app (optional).
Example:
/play?u=https%3A%2F%2Fbitmidi.com%2Fuploads%2F...mid&title=Hotel%20California&song=Hotel%20California
Main app CTA target:
/?song=<encodedQuery>
Why not share “result index”?
Because search ranking shifts; index links rot. If needed, index can be a fallback only when u is missing.
/play landing behavior
Required inputs
- Preview share:
u(MIDI source URL) - Export share:
m(motif midi id) ora(audio id)
State machine
- If
morapresent → load exported artifact - Else if
upresent → fetch + parse MIDI via backend proxy - Else → show “Invalid link” + CTA to home
Playback
- Never autoplay.
- Enable Audio CTA shown until AudioContext is running.
Error cases
- Missing/invalid params → show “Invalid link”
- Backend fetch failure → show “Couldn’t load this MIDI”
- Parse failure → show “Unsupported or corrupted MIDI”
Export-based sharing (generated Motif variants)
Sharing a generated Motif variant needs an artifact the landing page can load reliably:
- MIDI export: smaller, fast, consistent with the project (music-as-structure)
- Audio export: most universal listening, but heavier and needs careful encoding/hosting
Recommended sequencing
- MIDI export first (fast, small, easy to iterate)\n2) Add audio export once the flow proves value (and storage/limits are solved)
Proposed backend endpoints
1) Create export (returns a shareable play URL)
POST /api/share/export
Input (example):
{
"u": "https://bitmidi.com/uploads/....mid",
"song": "Hotel California",
"title": "Hotel California - Eagles",
"preset": "dance|ambient|ominous|default",
"params": { "intensity": 0.5, "swing": 0.1 },
"format": "midi|audio"
}
Output (example):
{
"id": "abc123",
"format": "midi",
"playUrl": "/play?m=abc123&title=Hotel%20California&song=Hotel%20California"
}
2) Fetch export artifact
Option A (explicit):
GET /api/share/artifact?id=abc123→ returns bytes (MIDI or audio)
Option B (format-specific):
GET /api/share/midi?id=abc123GET /api/share/audio?id=abc123
Landing params:
- MIDI artifact:
/play?m=<id>&title=...&song=... - Audio artifact:
/play?a=<id>&title=...&song=...
Storage strategy (dev vs production)
- Local dev: filesystem under
server/cache/exports/ - Vercel production: filesystem is not durable; prefer:
- blob storage (recommended), or
- KV/object store (if blob not available), or
- a small database row referencing a blob key
Security constraints (must-have)
- Validate
userver-side:- allow only
http/https - strongly consider allowlisting hosts (e.g.
bitmidi.com) for v1
- allow only
- Enforce limits:
- max MIDI bytes
- max parsed duration
- max export size
- max export time/CPU
- Rate-limit export endpoints.
- Cache and dedupe exports:
- key by
(sourceMidiHash + preset + params + format)to avoid repeated work
- key by
Files (expected) — implementation map
Frontend:
index.html: Share UI elementssrc/main.ts: URL generation + clipboard + X intentplay.html+src/play.ts: landing page UI + logicvite.config.ts: addplay.htmlto multi-page inputs
Backend:
server/src/server.ts: route handlers for export endpointsserver/src/services/*: reuse parsing/generation services as neededserver/cache/exports/: local dev artifact cache
Routing/deploy:
vercel.json: add/play→/play.htmlsimilar to/embed
Test plan
- Desktop:
- Share from main app → open
/play?...in new tab → loads → plays after click - Copy link works; X intent opens with correct URL
- Share from main app → open
- iOS Safari:
/playshows Enable Audio when needed- Tap Enable Audio → tap Play → sound
- Export flow:
- Export MIDI variant → receive
/play?m=...→ plays the exported MIDI reliably - Export audio variant (if implemented) →
/play?a=...→ plays reliably
- Export MIDI variant → receive
Success criteria
- A shared link reliably recreates “the thing” (preview or exported variant) and is playable with one tap.
- The system remains safe under abuse (no open proxy behavior, bounded cost).