Mobile: Video-Stories (Upload + Wiedergabe) #37

Open
opened 2026-09-19 19:20:20 +02:00 by admin · 0 comments
Owner

Plan: Video-Stories auf Mobile

Status: Planung (noch nicht umgesetzt) — "großes Mobile-Feature" nach WORKFLOW.md §9a (Plan zuerst, dann eigener Feature-Branch).
Ursprung: User-Report — beim Story-Erstellen auf Mobile ist im Gallery-Picker nur Foto auswählbar. Root Cause war keine Berechtigung, sondern eine bewusste, dokumentierte Design-Entscheidung: story-new.tsx ruft ImagePicker.launchImageLibraryAsync({ mediaTypes: ["images"] }) — Video-Stories existieren auf Mobile schlicht noch nicht (Kommentar im Code: "Photo-only story creation").

Gute Nachricht: Backend ist bereits fertig

src/trpc/routers/stories.tss createVideoUpload/getVideoStatus-Prozeduren existieren schon vollständig und sind generisch (nur petId-gebunden, kein Web-spezifischer Code) — sie werden aktuell nur von Web genutzt. Keine Backend-/Schema-Änderung nötig. Das ist eine reine Mobile-Client-Lücke, kein halbfertiges Feature.

Ablauf (bereits produktiv auf Web, src/components/stories/StoryForm.tsx):

  1. Client ruft createVideoUpload({ petId, aiDisclosure, musicTrackId?, musicMuteOriginal }) → Server legt bei Mux einen Direct Upload an (mux.video.uploads.create) und sofort eine Story-Row mit mediaType: "VIDEO", videoStatus: "PROCESSING". Antwort: { uploadUrl, storyId }.
  2. Client lädt die rohen Videobytes per einfachem PUT direkt zu uploadUrl hoch (Mux, nicht Supabase) — kein Chunking, keine spezielle Upload-Library nötig.
  3. Mux transkodiert asynchron, Webhook (/api/webhooks/mux) setzt videoStatus: "READY" + muxPlaybackId.
  4. Viewer pollt getVideoStatus({ storyId }) während PROCESSING (gleiches Selbstheilungs-Muster wie videos.getByPostId bei normalen Post-Videos).

Scope dieses Plans

Nur Video-Story-Upload + -Wiedergabe. Explizit nicht enthalten:

  • Musik-Overlay für Video-Stories auf Mobile — Mobile hat aktuell gar kein Musik-Overlay für Stories (weder Foto noch Video), das ist ein separates, bereits dokumentiertes, blockiertes Vorhaben (mobile/PENDING-NATIVE-BUILD.md, braucht expo-audio, kein natives Modul aktuell vorhanden). Dieser Plan fügt keine Musik-Picker-UI hinzu — musicTrackId wird beim createVideoUpload-Call einfach undefined gelassen, genau wie es Mobile heute schon bei Foto-Stories macht.
  • Video-Posts (normale Feed-Posts, nicht Stories) — Mobile kann heute nur Video-Posts ansehen (VideoPlayer.tsx), nicht hochladen. Bleibt unangetastet, kein Teil dieses Plans.

Umsetzungsschritte

1. mobile/app/(app)/story-new.tsx — Aufnahme/Auswahl

  • ImagePicker.launchImageLibraryAsync({ mediaTypes: ["images", "videos"], quality: 0.8 })mediaTypes um "videos" erweitern.
  • Erkennung Foto vs. Video: asset.duration != null (expo-image-picker liefert duration in Millisekunden für Videos, null/undefined für Fotos — zuverlässiger als ein MIME-Type-String, siehe die file.type-Lektion aus dem Web-Fix von eben).
  • Neue Konstante MAX_STORY_VIDEO_DURATION_SECS (Wert von src/lib/story-video-limits.ts auf Web spiegeln — kein gemeinsames Package zwischen Web/Mobile, daher wie bei MUSIC_ALLOWED_CONTENT_TYPES u. Ä. dupliziert, mit Kommentar der auf die Web-Quelle verweist) in einer neuen mobile/src/lib/story-video-limits.ts. Video ablehnen (Alert), wenn asset.duration das überschreitet.
  • Bei Video: createVideoUploadMutation.mutateAsync({ petId, aiDisclosure: "NONE" }){ uploadUrl, storyId }.
  • Upload der lokalen Video-URI zu uploadUrl per expo-file-system's File.upload() — dieselbe Primitive, die src/lib/upload.tss uploadImageToSupabase bereits für Fotos nutzt (PUT, kein Chunking). Neue Funktion (z. B. uploadFileToUrl(uri, url, mimeType)) statt die bestehende, funktionierende Bild-Funktion umzubauen — minimales Risiko für den bereits laufenden Foto-Pfad.
  • Nach erfolgreichem Upload: Screen schließen (Story ist serverseitig schon angelegt, PROCESSING) — kein eigenes Polling im Erstellungs-Screen nötig, das übernimmt der Viewer (siehe Schritt 2), analog zu Web.
  • UI: Vorschau wechselt bei Video auf expo-videos VideoView statt <Image> (kurze Preview, kein Ton nötig).

2. mobile/app/(app)/story-viewer.tsx — Wiedergabe

Aktuell komplett foto-only (eigener Kommentar im Code: "scoped to photo stories only"), fester 5-Sekunden-Timer treibt advance().

  • Branch auf currentStory.mediaType === "VIDEO".
  • Bei Video: trpc.stories.getVideoStatus pollen solange videoStatus === "PROCESSING" (identisches Muster zu VideoPlayer.tsxs Post-Video-Polling — refetchInterval nur während PROCESSING aktiv).
  • PROCESSING: Spinner (wie VideoPlayer.tsxs Processing-Box), Fortschrittsbalken pausiert.
  • READY: expo-videos VideoView mit https://stream.mux.com/${muxPlaybackId}.m3u8, kein Loop (anders als der Feed-VideoPlayer, der loopt — eine Story soll einmal durchlaufen und dann zur nächsten weiterschalten, wie Web es macht: dort treibt das Player-"ended"-Event den Wechsel, nicht der feste Timer).
  • Fortschrittsbalken bei Video: aus der tatsächlichen Wiedergabeposition ableiten (currentTime/duration) statt aus dem festen STORY_DURATION_MS-Timer — exaktes expo-video-Event/API für Zeit-Updates und Wiedergabe-Ende gegen die installierte Version (57.0.4) verifizieren, bevor implementiert wird (nicht aus dem Kopf annehmen).
  • ERROR: gleiche Fehler-Darstellung wie VideoPlayer.tsx.

3. i18n

  • screens/video.processing/video.error-Keys existieren auf Mobile schon (aus dem Post-Video-Viewer) — für den Viewer wiederverwendbar.
  • Neue Keys für die Erstellungs-Validierung: story.videoTooLong (oder analog zu Webs videoTooLong/videoTooLarge/videoUnreadable benannt) in de.json/en.json.

Geschätzter Umfang

  • 2 geänderte Screens (story-new.tsx, story-viewer.tsx)
  • 1 neue Konstanten-Datei (mobile/src/lib/story-video-limits.ts)
  • 1 neue kleine Upload-Hilfsfunktion in mobile/src/lib/upload.ts
  • 2 geänderte i18n-Dateien (neue Fehlermeldungs-Keys)
  • 0 Backend-/Schema-Änderungen

Risiken / offene Fragen

  • expo-video-API für Zeit-Updates/Ende-Event muss gegen die installierte Version geprüft werden, nicht angenommen — größte technische Unsicherheit in diesem Plan.
  • Kein RN-Testsetup (bekannte Lücke) — Verifikation bleibt Typecheck + Lint + manueller Test am Emulator/Gerät. Für den Emulator-Test müsste ein Beispiel-Video vorab in die virtuelle Galerie gepusht werden (adb push + Media-Scan-Trigger), da der Emulator keine echte Kamera-Galerie hat.
  • Aspect Ratio: Web berücksichtigt currentStory.aspectRatio fürs Layout (Hochkant-Videos vom Handy). Mobile-Viewer sollte das ebenfalls respektieren statt eines festen Seitenverhältnisses.
  • Dateigröße/Formate: Web erlaubt video/mp4,video/quicktime,video/webm bis 500 MB (dieselbe Grenze wie normale Video-Posts). Für Mobile-Stories reicht vermutlich ein kleineres Limit (kurze Stories) — genaue Zahl bei Umsetzung mit dem User klären, falls nicht einfach Webs Wert übernommen werden soll.

Nächster Schritt

Nach Freigabe: git checkout -b feature/mobile-video-stories, Umsetzung gemäß Schritte 1–3, Verifikation + Code-Review pro Commit (WORKFLOW.md §5/§6), Merge nach main erst wenn komplett fertig und manuell getestet.

# Plan: Video-Stories auf Mobile **Status:** Planung (noch nicht umgesetzt) — "großes Mobile-Feature" nach `WORKFLOW.md` §9a (Plan zuerst, dann eigener Feature-Branch). **Ursprung:** User-Report — beim Story-Erstellen auf Mobile ist im Gallery-Picker nur Foto auswählbar. Root Cause war keine Berechtigung, sondern eine bewusste, dokumentierte Design-Entscheidung: `story-new.tsx` ruft `ImagePicker.launchImageLibraryAsync({ mediaTypes: ["images"] })` — Video-Stories existieren auf Mobile schlicht noch nicht (Kommentar im Code: "Photo-only story creation"). ## Gute Nachricht: Backend ist bereits fertig `src/trpc/routers/stories.ts`s `createVideoUpload`/`getVideoStatus`-Prozeduren existieren schon vollständig und sind generisch (nur `petId`-gebunden, kein Web-spezifischer Code) — sie werden aktuell nur von Web genutzt. **Keine Backend-/Schema-Änderung nötig.** Das ist eine reine Mobile-Client-Lücke, kein halbfertiges Feature. Ablauf (bereits produktiv auf Web, `src/components/stories/StoryForm.tsx`): 1. Client ruft `createVideoUpload({ petId, aiDisclosure, musicTrackId?, musicMuteOriginal })` → Server legt bei Mux einen Direct Upload an (`mux.video.uploads.create`) und sofort eine `Story`-Row mit `mediaType: "VIDEO"`, `videoStatus: "PROCESSING"`. Antwort: `{ uploadUrl, storyId }`. 2. Client lädt die rohen Videobytes per einfachem `PUT` direkt zu `uploadUrl` hoch (Mux, nicht Supabase) — kein Chunking, keine spezielle Upload-Library nötig. 3. Mux transkodiert asynchron, Webhook (`/api/webhooks/mux`) setzt `videoStatus: "READY"` + `muxPlaybackId`. 4. Viewer pollt `getVideoStatus({ storyId })` während `PROCESSING` (gleiches Selbstheilungs-Muster wie `videos.getByPostId` bei normalen Post-Videos). ## Scope dieses Plans **Nur Video-Story-Upload + -Wiedergabe.** Explizit **nicht** enthalten: - **Musik-Overlay für Video-Stories auf Mobile** — Mobile hat aktuell *gar kein* Musik-Overlay für Stories (weder Foto noch Video), das ist ein separates, bereits dokumentiertes, blockiertes Vorhaben (`mobile/PENDING-NATIVE-BUILD.md`, braucht `expo-audio`, kein natives Modul aktuell vorhanden). Dieser Plan fügt keine Musik-Picker-UI hinzu — `musicTrackId` wird beim `createVideoUpload`-Call einfach `undefined` gelassen, genau wie es Mobile heute schon bei Foto-Stories macht. - Video-Posts (normale Feed-Posts, nicht Stories) — Mobile kann heute nur Video-Posts *ansehen* (`VideoPlayer.tsx`), nicht hochladen. Bleibt unangetastet, kein Teil dieses Plans. ## Umsetzungsschritte ### 1. `mobile/app/(app)/story-new.tsx` — Aufnahme/Auswahl - `ImagePicker.launchImageLibraryAsync({ mediaTypes: ["images", "videos"], quality: 0.8 })` — `mediaTypes` um `"videos"` erweitern. - Erkennung Foto vs. Video: `asset.duration != null` (expo-image-picker liefert `duration` in Millisekunden für Videos, `null`/`undefined` für Fotos — zuverlässiger als ein MIME-Type-String, siehe die `file.type`-Lektion aus dem Web-Fix von eben). - Neue Konstante `MAX_STORY_VIDEO_DURATION_SECS` (Wert von `src/lib/story-video-limits.ts` auf Web spiegeln — kein gemeinsames Package zwischen Web/Mobile, daher wie bei `MUSIC_ALLOWED_CONTENT_TYPES` u. Ä. dupliziert, mit Kommentar der auf die Web-Quelle verweist) in einer neuen `mobile/src/lib/story-video-limits.ts`. Video ablehnen (Alert), wenn `asset.duration` das überschreitet. - Bei Video: `createVideoUploadMutation.mutateAsync({ petId, aiDisclosure: "NONE" })` → `{ uploadUrl, storyId }`. - Upload der lokalen Video-URI zu `uploadUrl` per `expo-file-system`'s `File.upload()` — dieselbe Primitive, die `src/lib/upload.ts`s `uploadImageToSupabase` bereits für Fotos nutzt (PUT, kein Chunking). Neue Funktion (z. B. `uploadFileToUrl(uri, url, mimeType)`) statt die bestehende, funktionierende Bild-Funktion umzubauen — minimales Risiko für den bereits laufenden Foto-Pfad. - Nach erfolgreichem Upload: Screen schließen (Story ist serverseitig schon angelegt, `PROCESSING`) — kein eigenes Polling im Erstellungs-Screen nötig, das übernimmt der Viewer (siehe Schritt 2), analog zu Web. - UI: Vorschau wechselt bei Video auf `expo-video`s `VideoView` statt `<Image>` (kurze Preview, kein Ton nötig). ### 2. `mobile/app/(app)/story-viewer.tsx` — Wiedergabe Aktuell komplett foto-only (eigener Kommentar im Code: "scoped to photo stories only"), fester 5-Sekunden-Timer treibt `advance()`. - Branch auf `currentStory.mediaType === "VIDEO"`. - Bei Video: `trpc.stories.getVideoStatus` pollen solange `videoStatus === "PROCESSING"` (identisches Muster zu `VideoPlayer.tsx`s Post-Video-Polling — `refetchInterval` nur während PROCESSING aktiv). - `PROCESSING`: Spinner (wie `VideoPlayer.tsx`s Processing-Box), Fortschrittsbalken pausiert. - `READY`: `expo-video`s `VideoView` mit `https://stream.mux.com/${muxPlaybackId}.m3u8`, **kein Loop** (anders als der Feed-`VideoPlayer`, der loopt — eine Story soll einmal durchlaufen und dann zur nächsten weiterschalten, wie Web es macht: dort treibt das Player-"ended"-Event den Wechsel, nicht der feste Timer). - Fortschrittsbalken bei Video: aus der tatsächlichen Wiedergabeposition ableiten (`currentTime`/`duration`) statt aus dem festen `STORY_DURATION_MS`-Timer — **exaktes `expo-video`-Event/API für Zeit-Updates und Wiedergabe-Ende gegen die installierte Version (57.0.4) verifizieren**, bevor implementiert wird (nicht aus dem Kopf annehmen). - `ERROR`: gleiche Fehler-Darstellung wie `VideoPlayer.tsx`. ### 3. i18n - `screens`/`video.processing`/`video.error`-Keys existieren auf Mobile schon (aus dem Post-Video-Viewer) — für den Viewer wiederverwendbar. - Neue Keys für die Erstellungs-Validierung: `story.videoTooLong` (oder analog zu Webs `videoTooLong`/`videoTooLarge`/`videoUnreadable` benannt) in `de.json`/`en.json`. ### Geschätzter Umfang - 2 geänderte Screens (`story-new.tsx`, `story-viewer.tsx`) - 1 neue Konstanten-Datei (`mobile/src/lib/story-video-limits.ts`) - 1 neue kleine Upload-Hilfsfunktion in `mobile/src/lib/upload.ts` - 2 geänderte i18n-Dateien (neue Fehlermeldungs-Keys) - **0 Backend-/Schema-Änderungen** ## Risiken / offene Fragen - **`expo-video`-API für Zeit-Updates/Ende-Event** muss gegen die installierte Version geprüft werden, nicht angenommen — größte technische Unsicherheit in diesem Plan. - **Kein RN-Testsetup** (bekannte Lücke) — Verifikation bleibt Typecheck + Lint + manueller Test am Emulator/Gerät. Für den Emulator-Test müsste ein Beispiel-Video vorab in die virtuelle Galerie gepusht werden (`adb push` + Media-Scan-Trigger), da der Emulator keine echte Kamera-Galerie hat. - **Aspect Ratio**: Web berücksichtigt `currentStory.aspectRatio` fürs Layout (Hochkant-Videos vom Handy). Mobile-Viewer sollte das ebenfalls respektieren statt eines festen Seitenverhältnisses. - **Dateigröße/Formate**: Web erlaubt `video/mp4,video/quicktime,video/webm` bis 500 MB (dieselbe Grenze wie normale Video-Posts). Für Mobile-Stories reicht vermutlich ein kleineres Limit (kurze Stories) — genaue Zahl bei Umsetzung mit dem User klären, falls nicht einfach Webs Wert übernommen werden soll. ## Nächster Schritt Nach Freigabe: `git checkout -b feature/mobile-video-stories`, Umsetzung gemäß Schritte 1–3, Verifikation + Code-Review pro Commit (WORKFLOW.md §5/§6), Merge nach `main` erst wenn komplett fertig und manuell getestet.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/petfeed#37