or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

barcode-scanning.mdcamera-component.mdindex.mdmedia-capture.mdpermissions.md
tile.json

media-capture.mddocs/

Media Capture

Expo Camera provides comprehensive photo and video capture capabilities with extensive configuration options for quality, format, processing, and metadata handling across all supported platforms.

Capabilities

Photo Capture

Capture photos with comprehensive options for quality, format, and metadata.

/**
 * Capture a photo using the camera
 * @param options Configuration options for photo capture
 * @returns Promise resolving to captured photo data
 */
takePicture(options?: CameraPictureOptions): Promise<CameraCapturedPicture>;

Picture Capture Options

Complete configuration interface for photo capture.

interface CameraPictureOptions {
  /**
   * Image quality from 0 to 1 (0 = smallest size, 1 = highest quality)
   * @default 1
   */
  quality?: number;
  
  /**
   * Whether to include Base64 encoded image data
   * @default false
   */
  base64?: boolean;
  
  /**
   * Whether to include EXIF metadata
   * @default false
   */
  exif?: boolean;
  
  /**
   * Additional EXIF data to include (Android/iOS only)
   */
  additionalExif?: Record<string, any>;
  
  /**
   * Callback for when picture is saved (enables fast mode)
   * @param picture The captured picture data
   */
  onPictureSaved?: (picture: CameraCapturedPicture) => void;
  
  /**
   * Skip image processing for faster capture (may affect orientation)
   * @default false
   */
  skipProcessing?: boolean;
  
  /**
   * Image scale factor (Web only)
   */
  scale?: number;
  
  /**
   * Image format (Web only)
   */
  imageType?: ImageType;
  
  /**
   * Whether to mirror the image (Web only)
   */
  isImageMirror?: boolean;
  
  /**
   * Mirror front camera images (deprecated - use component mirror prop)
   * @deprecated Use mirror prop on CameraView instead
   */
  mirror?: boolean;
  
  /**
   * Enable camera shutter sound
   * @default true
   */
  shutterSound?: boolean;
  
  /**
   * Return a PictureRef instead of direct data
   * @default false
   */
  pictureRef?: boolean;
}

Captured Picture Data

Structure of captured photo data returned by takePicture.

interface CameraCapturedPicture {
  /**
   * Image width in pixels
   */
  width: number;
  
  /**
   * Image height in pixels
   */
  height: number;
  
  /**
   * Image format
   */
  format: 'jpg' | 'png';
  
  /**
   * File URI to the captured image
   * On web, same as base64 due to browser limitations
   */
  uri: string;
  
  /**
   * Base64 encoded image data (if base64 option was true)
   */
  base64?: string;
  
  /**
   * EXIF metadata (if exif option was true)
   * Web: Partial MediaTrackSettings
   * Mobile: Platform-specific metadata
   */
  exif?: Partial<MediaTrackSettings> | any;
}

Video Recording

Record video with configurable duration, file size, and codec options.

/**
 * Start video recording
 * @param options Configuration options for video recording
 * @returns Promise resolving to video file information
 */
record(options?: CameraRecordingOptions): Promise<{ uri: string }>;

/**
 * Stop current video recording
 * @returns Promise resolving when recording stops
 */
stopRecording(): Promise<void>;

/**
 * Toggle video recording state (iOS 18+ only)
 * @platform ios
 * @returns Promise resolving when toggle completes
 */
toggleRecording(): Promise<void>;

Video Recording Options

Configuration interface for video recording.

interface CameraRecordingOptions {
  /**
   * Maximum recording duration in seconds
   */
  maxDuration?: number;
  
  /**
   * Maximum file size in bytes
   */
  maxFileSize?: number;
  
  /**
   * Mirror front camera video (deprecated - use component mirror prop)
   * @deprecated Use mirror prop on CameraView instead
   */
  mirror?: boolean;
  
  /**
   * Video codec to use (iOS only)
   */
  codec?: VideoCodec;
}

Picture Reference Handling

Advanced native image reference handling for better performance.

/**
 * Capture photo and return a native image reference
 * @param options Configuration options for photo capture
 * @returns Promise resolving to native picture reference
 */
takePictureRef?(options?: CameraPictureOptions): Promise<PictureRef>;

/**
 * Native image reference class
 */
declare class PictureRef extends SharedRef<'image'> {
  /**
   * Image width in pixels
   */
  width: number;
  
  /**
   * Image height in pixels
   */
  height: number;
  
  /**
   * Save the image to file system
   * @param options Save configuration options
   * @returns Promise resolving to saved photo data
   */
  savePictureAsync(options?: SavePictureOptions): Promise<PhotoResult>;
}

interface SavePictureOptions {
  /**
   * Image quality from 0 to 1
   */
  quality?: number;
  
  /**
   * Additional metadata to include
   */
  metadata?: Record<string, any>;
  
  /**
   * Include Base64 data in result
   */
  base64?: boolean;
}

interface PhotoResult {
  /**
   * URI to the saved image
   */
  uri: string;
  
  /**
   * Image width
   */
  width: number;
  
  /**
   * Image height
   */
  height: number;
  
  /**
   * Base64 encoded data (if requested)
   */
  base64?: string;
}

Type Definitions

Supporting types for media capture.

type ImageType = 'png' | 'jpg';

type VideoCodec = 'avc1' | 'hvc1' | 'jpeg' | 'apcn' | 'ap4h';

Usage Examples

Basic Photo Capture

import React, { useRef } from 'react';
import { View, TouchableOpacity, Text, Alert, StyleSheet } from 'react-native';
import { CameraView, CameraPictureOptions } from 'expo-camera';

export function BasicPhotoCapture() {
  const cameraRef = useRef<CameraView>(null);

  const takePicture = async () => {
    if (!cameraRef.current) return;

    try {
      const options: CameraPictureOptions = {
        quality: 0.8,
        base64: false,
        exif: false,
      };
      
      const photo = await cameraRef.current.takePicture(options);
      
      Alert.alert('Photo Captured', `Saved to: ${photo.uri}`);
      console.log('Photo dimensions:', photo.width, 'x', photo.height);
      console.log('Photo format:', photo.format);
    } catch (error) {
      Alert.alert('Error', `Failed to take picture: ${error.message}`);
    }
  };

  return (
    <View style={styles.container}>
      <CameraView
        ref={cameraRef}
        style={styles.camera}
        facing="back"
      />
      <View style={styles.controls}>
        <TouchableOpacity style={styles.button} onPress={takePicture}>
          <Text style={styles.buttonText}>Take Picture</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  camera: { flex: 1 },
  controls: {
    position: 'absolute',
    bottom: 50,
    left: 0,
    right: 0,
    alignItems: 'center',
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 25,
    paddingHorizontal: 30,
  },
  buttonText: {
    color: 'white',
    fontWeight: 'bold',
    fontSize: 16,
  },
});

Advanced Photo Options

import React, { useRef, useState } from 'react';
import { View, TouchableOpacity, Text, Switch, StyleSheet } from 'react-native';
import { CameraView, CameraPictureOptions, CameraCapturedPicture } from 'expo-camera';

export function AdvancedPhotoCapture() {
  const cameraRef = useRef<CameraView>(null);
  const [includeBase64, setIncludeBase64] = useState(false);
  const [includeExif, setIncludeExif] = useState(false);
  const [quality, setQuality] = useState(0.8);

  const takePictureWithCallback = async () => {
    if (!cameraRef.current) return;

    const options: CameraPictureOptions = {
      quality,
      base64: includeBase64,
      exif: includeExif,
      additionalExif: {
        UserComment: 'Taken with Expo Camera',
        Software: 'MyApp v1.0.0',
      },
      shutterSound: true,
      onPictureSaved: (picture: CameraCapturedPicture) => {
        console.log('Picture saved callback:', picture.uri);
        // Process image immediately without waiting for full resolution
      },
    };

    try {
      // This will resolve immediately due to onPictureSaved callback
      const photo = await cameraRef.current.takePicture(options);
      console.log('Photo capture completed');
    } catch (error) {
      console.error('Photo capture failed:', error);
    }
  };

  const takePictureWithProcessing = async () => {
    if (!cameraRef.current) return;

    const options: CameraPictureOptions = {
      quality,
      base64: includeBase64,
      exif: includeExif,
      skipProcessing: false, // Full processing
    };

    try {
      const photo = await cameraRef.current.takePicture(options);
      
      console.log('Photo details:');
      console.log('- URI:', photo.uri);
      console.log('- Dimensions:', `${photo.width}x${photo.height}`);
      console.log('- Format:', photo.format);
      
      if (photo.base64) {
        console.log('- Base64 length:', photo.base64.length);
      }
      
      if (photo.exif) {
        console.log('- EXIF data available');
      }
    } catch (error) {
      console.error('Photo capture failed:', error);
    }
  };

  return (
    <View style={styles.container}>
      <CameraView
        ref={cameraRef}
        style={styles.camera}
        facing="back"
      />
      
      <View style={styles.options}>
        <View style={styles.optionRow}>
          <Text>Include Base64:</Text>
          <Switch value={includeBase64} onValueChange={setIncludeBase64} />
        </View>
        
        <View style={styles.optionRow}>
          <Text>Include EXIF:</Text>
          <Switch value={includeExif} onValueChange={setIncludeExif} />
        </View>
        
        <Text>Quality: {quality.toFixed(1)}</Text>
      </View>
      
      <View style={styles.controls}>
        <TouchableOpacity style={styles.button} onPress={takePictureWithCallback}>
          <Text style={styles.buttonText}>Fast Capture</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.button} onPress={takePictureWithProcessing}>
          <Text style={styles.buttonText}>Full Quality</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  camera: { flex: 1 },
  options: {
    position: 'absolute',
    top: 100,
    left: 20,
    backgroundColor: 'rgba(0,0,0,0.7)',
    padding: 15,
    borderRadius: 10,
  },
  optionRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 10,
    minWidth: 150,
  },
  controls: {
    position: 'absolute',
    bottom: 50,
    left: 0,
    right: 0,
    flexDirection: 'row',
    justifyContent: 'space-around',
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 25,
  },
  buttonText: {
    color: 'white',
    fontWeight: 'bold',
  },
});

Video Recording

import React, { useRef, useState } from 'react';
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { CameraView, CameraRecordingOptions } from 'expo-camera';

export function VideoRecording() {
  const cameraRef = useRef<CameraView>(null);
  const [isRecording, setIsRecording] = useState(false);
  const [recordingTime, setRecordingTime] = useState(0);

  const startRecording = async () => {
    if (!cameraRef.current || isRecording) return;

    try {
      setIsRecording(true);
      setRecordingTime(0);

      const options: CameraRecordingOptions = {
        maxDuration: 30, // 30 seconds max
        maxFileSize: 50 * 1024 * 1024, // 50MB max
        codec: 'avc1', // H.264 codec (iOS only)
      };

      // Start timer
      const timer = setInterval(() => {
        setRecordingTime(prev => prev + 1);
      }, 1000);

      const video = await cameraRef.current.record(options);
      
      clearInterval(timer);
      setIsRecording(false);
      
      console.log('Video recorded:', video.uri);
      console.log('Recording duration:', recordingTime, 'seconds');
    } catch (error) {
      setIsRecording(false);
      console.error('Recording failed:', error);
    }
  };

  const stopRecording = async () => {
    if (!cameraRef.current || !isRecording) return;

    try {
      await cameraRef.current.stopRecording();
      setIsRecording(false);
    } catch (error) {
      console.error('Failed to stop recording:', error);
    }
  };

  const toggleRecording = async () => {
    if (!cameraRef.current) return;

    try {
      await cameraRef.current.toggleRecording();
      setIsRecording(prev => !prev);
    } catch (error) {
      console.error('Failed to toggle recording:', error);
    }
  };

  return (
    <View style={styles.container}>
      <CameraView
        ref={cameraRef}
        style={styles.camera}
        facing="back"
        mode="video"
      />
      
      {isRecording && (
        <View style={styles.recordingIndicator}>
          <View style={styles.recordingDot} />
          <Text style={styles.recordingText}>
            Recording: {Math.floor(recordingTime / 60)}:{(recordingTime % 60).toString().padStart(2, '0')}
          </Text>
        </View>
      )}
      
      <View style={styles.controls}>
        <TouchableOpacity
          style={[styles.button, isRecording && styles.recordingButton]}
          onPress={isRecording ? stopRecording : startRecording}
        >
          <Text style={styles.buttonText}>
            {isRecording ? 'Stop' : 'Record'}
          </Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.button} onPress={toggleRecording}>
          <Text style={styles.buttonText}>Toggle</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  camera: { flex: 1 },
  recordingIndicator: {
    position: 'absolute',
    top: 100,
    left: 20,
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: 'rgba(255, 0, 0, 0.8)',
    padding: 10,
    borderRadius: 20,
  },
  recordingDot: {
    width: 12,
    height: 12,
    borderRadius: 6,
    backgroundColor: 'white',
    marginRight: 8,
  },
  recordingText: {
    color: 'white',
    fontWeight: 'bold',
  },
  controls: {
    position: 'absolute',
    bottom: 50,
    left: 0,
    right: 0,
    flexDirection: 'row',
    justifyContent: 'space-around',
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 25,
    minWidth: 80,
    alignItems: 'center',
  },
  recordingButton: {
    backgroundColor: '#FF3B30',
  },
  buttonText: {
    color: 'white',
    fontWeight: 'bold',
  },
});

Picture Reference Usage

import React, { useRef } from 'react';
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { CameraView, CameraPictureOptions, PictureRef, SavePictureOptions } from 'expo-camera';

export function PictureReferenceExample() {
  const cameraRef = useRef<CameraView>(null);

  const takePictureWithRef = async () => {
    if (!cameraRef.current?.takePictureRef) {
      console.warn('PictureRef not supported on this platform');
      return;
    }

    try {
      const options: CameraPictureOptions = {
        quality: 1.0,
        pictureRef: true,
      };

      const pictureRef = await cameraRef.current.takePictureRef(options);
      
      console.log('Picture reference created:');
      console.log('- Width:', pictureRef.width);
      console.log('- Height:', pictureRef.height);

      // Save the picture with additional processing
      const saveOptions: SavePictureOptions = {
        quality: 0.9,
        base64: true,
        metadata: {
          title: 'My Photo',
          description: 'Taken with PictureRef',
        },
      };

      const savedPhoto = await pictureRef.savePictureAsync(saveOptions);
      
      console.log('Photo saved:');
      console.log('- URI:', savedPhoto.uri);
      console.log('- Dimensions:', `${savedPhoto.width}x${savedPhoto.height}`);
      if (savedPhoto.base64) {
        console.log('- Base64 available');
      }
    } catch (error) {
      console.error('PictureRef capture failed:', error);
    }
  };

  return (
    <View style={styles.container}>
      <CameraView
        ref={cameraRef}
        style={styles.camera}
        facing="back"
      />
      
      <View style={styles.controls}>
        <TouchableOpacity style={styles.button} onPress={takePictureWithRef}>
          <Text style={styles.buttonText}>Take with PictureRef</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  camera: { flex: 1 },
  controls: {
    position: 'absolute',
    bottom: 50,
    left: 0,
    right: 0,
    alignItems: 'center',
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 25,
  },
  buttonText: {
    color: 'white',
    fontWeight: 'bold',
  },
});