Android SharedPreferences Tutorial and Example

Capturing video with camera intent in Android

Capturing short video clips directly from within an app used to require juggling the Camera2 API, SurfaceViews, MediaRecorder sessions, and a stack of lifecycle callbacks. For many use cases, such as uploading a quick product review, attaching a clip to a support ticket, or letting a user record a moment in a social app, the platform already provides a much simpler path. By firing an ACTION_VIDEO_CAPTURE intent, you can hand the whole recording job to the system camera and receive a content URI back when the user is done.

This walkthrough focuses on the practical wiring needed to record video with a camera intent on Android, covering permissions, the launch and result contract, file storage options, and the small handful of bugs that tend to bite developers in production. Whether you are building for users in Sydney, Melbourne, or a regional town, the steps below work the same way; the only real variables are device firmware and the user's network when they choose to upload what they just shot.

Manifest permissions and runtime checks

Every video capture flow starts in AndroidManifest.xml. Even though the system camera does the actual recording, you still need to declare <uses-feature android:name="android.hardware.camera" android:required="false" /> so the Play Store does not filter out tablets and Chromebooks that lack a rear camera. If you want high-quality output, also add a non-required android.hardware.microphone feature.

On the runtime side, you do not need to request CAMERA permission yourself, because the system camera app acts as a separate process and handles its own authorisation. You do need READ_MEDIA_VIDEO (API 33+) or READ_EXTERNAL_STORAGE (API 28 and below) to open the returned URI afterwards, and POST_NOTIFICATIONS if you plan to show a foreground service notification while uploading in the background. For Australian users on Telstra or Optus networks, background uploads can chew through mobile data quickly, so gate any auto-upload behind a Wi-Fi check before kicking it off.

Launching the built-in camera with the correct intent

The intent itself is straightforward. Build an Intent(MediaStore.ACTION_VIDEO_CAPTURE) and pass any extras you care about. The three most useful are MediaStore.EXTRA_DURATION_LIMIT (in seconds), MediaStore.EXTRA_SIZE_LIMIT (in bytes), and MediaStore.EXTRA_VIDEO_QUALITY, which takes either 0 for low or 1 for high.

Wrap the call in the modern ActivityResultLauncher<Intent> API rather than the deprecated startActivityForResult. The launcher belongs inside your Activity or Fragment, and registering it in the same lifecycle as your view model keeps the contract out of the way of configuration changes. A common pattern in Australian development teams, especially in agencies shipping for JB Hi-Fi's commercial clients, is to keep the launcher in the Fragment so it survives screen rotations on devices like the Samsung Galaxy A-series that are popular in retail channels.

private val videoLauncher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        val uri = result.data?.data
        // handle the clip
    }
}

Always provide a FileProvider URI when you pre-allocate a destination, because Intent.FLAG_GRANT_WRITE_URI_PERMISSION only works with content:// URIs. If you skip the provider, the camera app will crash with a SecurityException on Android 10 and above.

Receiving the video URI and reading the file

When the user finishes recording or cancels, the camera app returns through your launcher with a resultCode and a data Intent whose getData() method exposes a content:// URI. The URI may point to a MediaStore entry on Android 11+ or to a FileProvider path you supplied earlier. Treat it as opaque; never assume the path component maps to a real on-disk file.

To display a thumbnail, use MediaMetadataRetriever and call getFrameAtTime(). For playback, hand the URI straight to an ExoPlayer or the platform VideoView, as both handle content schemes natively. If you intend to keep the clip beyond the app session, copy the bytes into your app's private storage with ContentResolver.openInputStream and write them to a File under getExternalFilesDir(Environment.DIRECTORY_MOVIES). Remember that MediaStore entries created through an intent can disappear if the user clears the camera app's cache or uninstalls it, which is a frequent source of "missing video" bug reports from users in the bush who rely on entry-level handsets with limited storage.

Comparing video capture approaches

Approach Code complexity Customisation User control Storage location
ACTION_VIDEO_CAPTURE intent Low Minimal (duration, size, quality) User picks camera app App-provided URI or MediaStore
Camera2 API with MediaRecorder High Full (frame rate, bitrate, encoder profile) App-controlled Any path the app can write
CameraX VideoCapture use case Medium High with managed lifecycle App-controlled Any path the app can write
Third-party SDK (e.g. FFmpegKit) High Total App-controlled Any path the app can write

For prototypes, support tools, and most consumer apps, the intent approach delivers the fastest path to a working feature. The Camera2 and CameraX routes become worthwhile when you need frame-accurate control, custom overlays, or low-latency streaming, for example, in a coaching app where the coach records a surfer paddling out at Bondi Beach and wants to mark moments in real time.

Common pitfalls and how to dodge them

A handful of issues show up again and again. The first is forgetting that the camera intent can return resultCode == RESULT_CANCELED even when the user successfully recorded something, on some OEM builds. Always check the URI before assuming success. The second is the silent failure on devices that disable the camera app entirely, which is common on corporate-managed devices in Australian banks and government departments, so provide a clear error message and, where possible, a manual picker fallback.

Third, do not rely on Environment.getExternalStoragePublicDirectory for the output file. Direct external paths are restricted since API 29, and your intent will throw on Android 11+. Always use a FileProvider URI or let the camera write to MediaStore on your behalf. Fourth, watch out for orientation, because videos saved by the camera carry their rotation in metadata, and if your preview thumbnail comes out sideways, that is almost always the cause.

Finally, if your workflow involves moving the captured clip to a backend or NAS at home, a flaky local network can leave users with a half-uploaded file. Encourage uploads over Wi-Fi, and consider retrying with exponential backoff. For developers testing in shared offices around Brisbane's tech corridor, getting the home router sorted before a long recording session matters more than people think, and a WPS extender setup guide can save an afternoon of dropped uploads from the back room where the signal is weak.

Recommendations for a robust capture flow

  • Use ActivityResultLauncher registered at the Fragment or Activity level, never the deprecated onActivityResult override.
  • Always declare <queries> for android.media.action.VIDEO_CAPTURE so your intent resolves on Android 11+ package visibility rules.
  • Pre-allocate a destination through a FileProvider when you need a guaranteed file path; otherwise, let the camera create a MediaStore entry.
  • Cap EXTRA_DURATION_LIMIT at a sensible value, often 60 seconds, to avoid users filling their device with multi-gigabyte test clips.
  • Compress or transcode before uploading if your target audience relies on regional 4G in places like Cairns or Darwin, where speeds can vary widely.