diff --git a/src/components/stories/StoryForm.tsx b/src/components/stories/StoryForm.tsx index 8783da2..92c5770 100644 --- a/src/components/stories/StoryForm.tsx +++ b/src/components/stories/StoryForm.tsx @@ -23,7 +23,33 @@ const MAX_IMAGE_SIZE = 10 * 1024 * 1024; const ALLOWED_VIDEO_TYPES = ["video/mp4", "video/quicktime", "video/webm"] as const; const MAX_VIDEO_SIZE = 500 * 1024 * 1024; // same as normal video posts (VideoUploadForm.tsx) -const ACCEPT_ATTR = [...ALLOWED_IMAGE_TYPES, ...ALLOWED_VIDEO_TYPES].join(","); +// Wildcard categories, not the explicit MIME list (`image/jpeg,...,video/mp4,...`) +// — several mobile browsers'/OS's native "Photos & Videos" pickers only +// reliably offer *both* media kinds when given the broad `image/*`/`video/*` +// categories; a long explicit list mixing image and video subtypes was +// silently narrowing the picker down to photos only on at least one such +// picker (user report: "beim Story erstellen nur Zugriff auf Fotos, Videos +// werden gar nicht angezeigt"). This only relaxes what the OS *offers* in +// the picker dialog — the actual accept/reject decision still happens in +// handleFile() below against the specific +// ALLOWED_IMAGE_TYPES/ALLOWED_VIDEO_TYPES lists, unchanged. The two are +// deliberately decoupled now (this is a fixed category string, not derived +// from those lists) — a wildcard category can't meaningfully express "just +// these three video subtypes" to the OS picker, so narrowing +// ALLOWED_VIDEO_TYPES later won't narrow what the picker offers; it'll +// still be enforced right here after selection, just with an extra +// pick-then-reject round trip for a now-disallowed type — an accepted +// trade-off for actually being able to pick a video at all. Two related, +// deliberately-not-fixed-here risks (flagged in code review): (1) the OS +// picker now also surfaces formats this form never accepted (GIF, MKV, +// 3GPP, ...), each ending in the same pick-then-reject round trip; (2) iOS +// Safari's HEIC->JPEG auto-transcode for gallery photos is reportedly tied +// to the accept list being specific — `image/*` may let a raw HEIC file +// through where `image/jpeg,image/png,image/webp` didn't, and +// ALLOWED_IMAGE_TYPES doesn't list `image/heic`/`image/heif`. Neither is +// confirmed against a real device in this session; revisit if iPhone users +// report photo uploads failing here specifically. +const ACCEPT_ATTR = "image/*,video/*"; interface StoryFormProps { onClose: () => void; @@ -181,8 +207,15 @@ export function StoryForm({ onClose }: StoryFormProps) { } setErrorMessage(null); - const isVideo = (ALLOWED_VIDEO_TYPES as readonly string[]).includes(file.type); - const isImage = (ALLOWED_IMAGE_TYPES as readonly string[]).includes(file.type); + // Strip codec parameters before comparing — some Android camera-roll + // pickers report e.g. `video/mp4;codecs=hvc1` for HEVC-recorded clips, + // which never matched the bare `ALLOWED_VIDEO_TYPES` strings. This was + // unreachable while the accept attribute only offered photos (see + // ACCEPT_ATTR above); now that videos are actually selectable, a real + // mp4 could otherwise still get rejected as "invalid type" here. + const bareMimeType = file.type.split(";")[0].trim().toLowerCase(); + const isVideo = (ALLOWED_VIDEO_TYPES as readonly string[]).includes(bareMimeType); + const isImage = (ALLOWED_IMAGE_TYPES as readonly string[]).includes(bareMimeType); if (!isVideo && !isImage) { setErrorMessage(t("invalidType"));