The idea
Patrons can send a photo or short video from pub.ihnyc-rc.org/up. A DJ normally reviews it, then the media briefly fills the projector before the regular visual resumes.
flowchart LR PHONE["Patron phone<br/>pick and preview"] --> UPLOAD["Upload"] UPLOAD --> REVIEW{"DJ review<br/>unless auto-approve is on"} REVIEW -- Reject --> GONE["Remove media"] REVIEW -- Approve --> READY["Prepare for projector"] READY --> WALL["Show briefly on wall"] WALL --> HISTORY["Keep history and media"]
Operators use /admin/dj-mode to review patron uploads, remove approved items, upload their own media, choose the display duration, and control the rest of DJ Mode.
What the patron sees
The /up page has three simple states:
- pick a photo or video;
- preview it and confirm Send to the wall;
- receive success or a retryable error.
The Worker independently checks the file type and enforces a 50 MB maximum. It recognizes common phone image and video containers even when an in-app browser reports an empty or generic MIME type.
| Media | Preparation |
|---|---|
| JPEG, PNG, WebP, GIF | Stored directly for the wall |
| HEIC, HEIF, AVIF, BMP, TIFF, or another non-native image | Converted to JPEG with Cloudflare Images; rejected if a projector-safe copy cannot be made |
| MP4, MOV, WebM | Converted to a silent, projector-safe H.264 MP4 before playback |
Moderation and auto-approve
The safe default is moderation. flash:config.autoApprovePatron in DJ_FLASH KV changes only that decision:
flowchart TD NEW["New upload"] --> AUTO{"Auto-approve?"} AUTO -- No --> PENDING["D1 status: pending"] PENDING --> DJ{"DJ decision"} DJ -- Reject --> REJECTED["D1 status: rejected<br/>R2 media deleted"] DJ -- Approve --> PROCESS["queued image or<br/>transcoding video"] AUTO -- Yes --> PROCESS PROCESS --> SHOWN["D1 status: shown"]
Auto-approved items retain source: patron-auto, so history can distinguish them from DJ uploads. Videos still have to finish processing; auto-approve does not bypass media conversion.
The DJ can also select up to 20 files from the admin page. Each file is accepted or rejected independently, so one unsupported item does not fail the rest of the batch.
Video processing
Every accepted video enters D1 with status: transcoding. The projector skips that row until a safe MP4 exists.
flowchart LR RAW["Raw phone video in R2"] --> EDGE["Cloudflare Media<br/>8-second silent H.264 MP4"] EDGE -- Success --> QUEUED["D1 status: queued"] EDGE -- Unavailable or failed --> WAIT["Short grace period"] WAIT --> PI["On-site Pi + ffmpeg fallback"] PI -- Success --> QUEUED PI -- Failure or 10-minute timeout --> FAILED["D1 status: failed<br/>source preserved"]
Cloudflare Media gets the first attempt. If it is unavailable or fails, the on-site dj-flash-transcoder polls for work, downloads the source, runs ffmpeg, and posts the MP4 back. The Pi uses outbound requests only; it does not expose an inbound service.
Relevant machine endpoints are Bearer-protected with the same ingest credential used by the Pub Pi:
| Endpoint | Purpose |
|---|---|
GET /api/dj-flash/pending-transcodes | List eligible D1 rows waiting for the Pi fallback |
POST /api/dj-flash/transcoded/:id | Upload the finished MP4 and mark the row queued |
POST /api/dj-flash/transcode-failed/:id | Mark the row failed without repeatedly retrying it |
Queue and display
The durable queue is the D1 flash_uploads table, not a JSON array in KV. Atomic row inserts prevent two simultaneous phones from overwriting one another.
| Status | Meaning |
|---|---|
pending | Waiting for DJ review |
transcoding | Approved video still being prepared |
queued | Ready for the wall |
shown | Played on the wall |
rejected | DJ rejected it before approval |
dequeued | DJ removed it after approval |
failed | Media preparation failed or timed out |
DJ_FLASH KV now holds only short-lived coordination values: the active item, the last-finished time used to space flashes, and the auto-approve configuration.
The /dj-now-playing projector listens to the DJ Mode SSE stream. It selects the oldest ready item, skips anything still transcoding, and displays the media for its chosen duration. The default is eight seconds, with a minimum gap between consecutive flashes.
Storage and retention
R2 (DJ_VISUALS) stores the bytes under these lanes:
patron/pending/<uuid>.<ext> awaiting review
patron/pending/processed/<uuid>.mp4 safe preview for a pending video
patron/approved/<uuid>.<ext> approved patron media
crowd/<uuid>.<ext> DJ or auto-approved media
crowd/<uuid>.mp4 final safe videoD1 holds the state, file key, MIME type, source, size, duration, timestamps, and audit request metadata. Current upload handling records the request IP address and user agent on the flash_uploads row.
Current retention behavior
Automatic deletion of shown
/upmedia is disabled. Shown media and failed-transcode sources are preserved; an explicit reject or dequeue removes the associated R2 object. The public privacy-policy wording for this flow is under human review in docs issue #50.
Operator checks
An upload did not appear
- Check Pending moderation when auto-approve is off.
- Check the item’s D1 status in the DJ Mode history.
- For
transcoding, verify Cloudflare Media and thedj-flash-transcoderservice. - A row left transcoding for ten minutes becomes
failed; its source remains available for diagnosis. - Check the projector console only after the row is
queued.
Something inappropriate was submitted
- Reject a pending item to delete it.
- Dequeue an approved item to remove it before display.
- Turn auto-approve off immediately; items already accepted remain in their current state.
There is no patron block-list UI in the current code. Do not improvise one from request metadata in public documentation or chat.
Sources
ihnyc-avi-pub-landing/worker.js— upload, moderation, Media, SSE, and Pi endpointsihnyc-avi-pub-landing/worker/lib/flash-uploads-store.js— D1 lifecycle and historyihnyc-avi-pub-landing/migrations/0026_flash_uploads.sql— durable queueihnyc-avi-pub-landing/migrations/0051_dj_flash_media_retention_off.sql— retention changeihnyc-avi-pub-landing/pi/dj-flash-transcoder/main.py— Pi fallbackihnyc-avi-pub-landing/public/up/index.htmlandpublic/dj-now-playing/index.html— patron and projector surfaces