Device-Side Compression and Uploads
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Baseline date: 2026-10-07. A phone camera produces files that are far larger than any screen needs: a 12 to 48 megapixel photo is several megabytes, and a minute of 4K video can be hundreds of megabytes. Sending the original over a mobile connection is slow, expensive for the user and fragile. Compressing on the device first helps, but the server still has the final word, see upload pipelines.
What to do on the device, and what not to
| On the device | On the server | |
|---|---|---|
Images |
Downscale to the largest size the product needs (for example 2048 pixels on the long side), apply the EXIF rotation, and encode as JPEG or WebP at a sane quality. Convert HEIC to JPEG only if the backend cannot read HEIC. |
Validate the file, strip metadata, produce all variants and formats, compute the BlurHash, see image formats. |
Video |
Optionally transcode to a moderate resolution and bit rate, to shorten the upload. |
Always transcode to the ladder and package for streaming, see video encoding fundamentals. |
Metadata and privacy |
Strip location if the user did not intend to share it. |
Strip it again. Never rely on the client for security. |
Rules of thumb:
-
The server must never trust the client’s work: it validates and re-encodes everything. Device-side compression is an optimisation of the upload only.
-
Compress when the user is on mobile data or the file is big. A small, already compressed JPEG gains little, and each re-encode loses quality (generation loss).
-
Keep the original when the product needs it (for example a photo editor) and upload a compressed copy for previews.
-
Do not block the UI: compression takes seconds for images and minutes for video, and drains the battery.
HEIC and HEVC from iOS
iPhones capture HEIC photos and HEVC video by default. The files are small and not widely playable outside Apple platforms. Two options:
-
Ask iOS for a compatible format. Set the picker or the camera to export compatible formats (JPEG, H.264), at the cost of larger files.
PHPickerConfiguration.preferredAssetRepresentationMode = .compatibleandUIImagePickerController.videoExportPresetchoose what is delivered. See Camera, Media and Photos and AVFoundation. -
Upload HEIC and HEVC unchanged, and convert on the server. This keeps the smallest upload. The server needs a libheif and an HEVC-capable FFmpeg, which have licensing implications, see image formats and encoding and video encoding fundamentals.
Android phones record H.264 or HEVC, and many save images as JPEG, with HEIC and AVIF available on newer devices.
Compressing images
| Platform | Tool |
|---|---|
Android |
Decode with the size of the target ( |
iOS |
ImageIO thumbnails with |
React Native, Expo |
|
Flutter |
|
Use the ImageIO and ImageDecoder downsampling paths rather than decoding the full image and then scaling, which can run
out of memory on a 48-megapixel photo. See the Bitmap section of
Image loading with Coil and Glide.
Compressing video
Android: Media3 Transformer, see Media3. iOS: AVAssetExportSession
presets, see AVFoundation. Cross-platform libraries wrap those
(for example react-native-compressor). Choose H.264 at 720p as the target when you transcode on the device, which every
server-side pipeline can read. Device encoders vary in quality, so treat their output as an upload format, not as a
delivery format.
Uploading
Upload straight to object storage with a pre-signed URL from the API, as in upload pipelines. The app then tells the API that the upload is complete, or the storage event triggers processing.
Background uploads
A user will lock the phone or switch apps during a large upload. Use the platform’s background transfer service:
-
iOS. A background
URLSessionconfiguration, withuploadTask(with:fromFile:). The system continues the transfer after the app is suspended and relaunches it on completion, see AVFoundation for the code. -
Android. WorkManager with a
CoroutineWorkermarked as a long-running foreground worker (setForeground), orDownloadManagerfor downloads. WorkManager retries with backoff and survives process death. Reference: WorkManager and long-running workers. -
React Native and Expo.
expo-file-systemupload tasks with background support, orreact-native-background-upload, both of which wrap the native services.
Resumable and multipart uploads
For files of tens of megabytes or more, a dropped connection should not restart the upload from zero:
-
S3 multipart upload, Google Cloud Storage resumable uploads and Azure Blob block uploads split the file into parts that are uploaded independently and retried. The API signs one URL per part (S3) or returns a session URL (Google).
-
tus (tus.io) is an open protocol for resumable uploads with client libraries for the main platforms, for backends that do not use object storage directly.
-
Track which parts finished so the app can resume after a restart, and show real progress from the part count.
Progress and failure
Show progress from the transfer callbacks, and the server-side processing state afterwards (processing, ready,
failed) by polling or from a push notification, see
upload pipelines. Retry transient failures with
backoff, keep the compressed file until the API confirms the upload, and let the user cancel. Pre-signed URLs expire, so
ask for a new one when a retry happens after the expiry.