Skip to main content

ADR 0002: Chunk Videos Client-side Before Upload

  • Status: Superseded / deferred
  • Date: 2025-10-03
  • Superseded on: 2026-05-08
  • Deciders: Akshay
  • Tags: frontend, pipeline, ux

Contextโ€‹

Users record long practice sessions (15-60 minutes). A single 60-minute 1080p video is ~5 GB. Uploading that as one blob is hostile:

  • Mobile uploads are unreliable past ~500 MB โ€” partial failures are frequent
  • GPU processing a 60-min video on one worker takes > 10 min โ€” users stare at a spinner
  • Memory spikes on RTX 4090 at long durations โ€” risk of OOM
  • Retrying a failure means re-uploading the whole thing

Options Consideredโ€‹

A. Upload whole video, chunk server-sideโ€‹

  • Pros: Simple client; no FFmpeg on device
  • Cons: User waits for the whole upload before processing starts; mobile network unreliability; extra GCS egress when Functions re-download the blob

B. Resumable upload with server-side chunkingโ€‹

  • Pros: Partial-failure recovery; single upload URL
  • Cons: Still serial processing; doesn't help GPU OOM; more server complexity

C. Client-side chunking + parallel uploadโ€‹

  • Pros: Processing can start on chunk 1 while chunk 2 is still uploading; per-chunk retry; bounded GPU memory; better user perception of progress
  • Cons: Requires FFmpeg on device (adds ~40 MB to Flutter bundle); merge logic on the server; overlap handling for events straddling chunk boundaries

Current Production Stateโ€‹

This ADR described the intended chunking architecture, but the shipped Flutter upload path currently sends a single video to the requestUploadPath / processVideoOnUpload pipeline. The backend still keeps sessionId, chunkIndex, totalChunks, isChunked, mergeAnalyses, and mergeChunkResults so the architecture can support chunking later. requestUploadPath creates one job with chunkIndex: 0, totalChunks: 1, and isChunked: false; the client does not ship FFmpeg-based splitting.

Until the client-side FFmpeg implementation is actually wired and verified, the launch contract is: one uploaded video produces one post-match report.

Original Decisionโ€‹

Chunk videos client-side into 5-minute segments with 3-second overlap, using FFmpeg FFI in Flutter.

Rationale:

  • 5-min chunks keep individual jobs < 3 min of GPU time โ†’ fits comfortably in RunPod's 10-min execution budget
  • 3-second overlap ensures that shots/bounces straddling a chunk boundary are visible in at least one chunk; the mergeChunkResults function deduplicates
  • Processing starts as soon as chunk 1 finalizes โ†’ user sees progress immediately
  • Per-chunk retry works naturally via Firebase Storage's resumable upload

Consequencesโ€‹

Positiveโ€‹

  • These are benefits of the original proposal, not current product behavior.
  • Server-side merge helpers remain reusable if physical chunking is revisited.

Negativeโ€‹

  • FFmpeg bloats the Flutter bundle by ~40 MB per platform
  • Shot detection at chunk seams needs careful dedup logic (deduplicateShots in mergeChunkResults)
  • sessionId must be passed through the whole pipeline so merging works
  • Client-side chunking is CPU-intensive on low-end devices (can take 30-60s for a 15-min video)

Neutralโ€‹

  • Introduces the concept of "session" (group of jobs) vs. "job" (single chunk) in the data model
  • Adds one more Firestore query to reconstruct session state in the UI

Follow-up Actionsโ€‹

  • Integrate ffmpeg_kit_flutter via FFI or choose a lighter native trim/split implementation
  • Implement chunk upload queue with retry
  • Write mergeChunkResults callable
  • Document overlap dedup in acesense-auth-function/video/index.ts
  • Benchmark chunking time on low-end Android to decide if we need to split it
  • Consider lazy FFmpeg loading โ€” only bundle when user starts their first upload
  • Document the shipped contract as one upload โ†’ one job/report

Referencesโ€‹