Crea un proyecto de Expo SDK 57 e instala los paquetes media principales en los que se construye esta sección:
npx create-expo-app@latest MediaBasics --template default@sdk-57
cd MediaBasics
npx expo install expo-image expo-camera expo-video
{
"dependencies" : {
"expo" : "~57.0.4" ,
"react" : "19.2.3" ,
"react-native" : "0.86.0"
}
}
Herramientas: Estos ejemplos apuntan a Expo SDK 57 (expo ~57.0.4), React Native 0.86.0 , y React 19.2.3 .
Antes de importar un paquete, clasifica la pantalla. La captura, reproducción y edición tienen diferentes stacks nativos y perfiles de memoria.
Captura - hardware de cámara, permisos, URIs de archivos temporales
Reproducción - decodificadores, buffering, sesión de audio, modos de fondo
Edición - transformaciones de CPU/GPU, a menudo bloqueantes a menos que se descarguen
Mostrar - vistas de imagen, caché, reciclado en listas
// src/media/moduleMap.ts
export const mediaModules = {
capture: {
livePreview: "expo-camera" ,
libraryPick: "expo-image-picker" ,
systemCameraIntent: "expo-image-picker.launchCameraAsync" ,
},
display: {
remoteAndLocal: "expo-image" ,
legacyOnly: "react-native Image" , // evitar para nuevos feeds
},
playback: {
video: "expo-video" ,
audioOnly: "expo-audio" ,
},
edit: {
resizeCompress: "expo-image-manipulator" ,
heavyVideo: "native pipeline or cloud transcode" ,
},
} as const ;
Una pantalla, un trabajo principal - mezclar previsualización de captura y reproducción de video en el mismo árbol duplica la presión de GPU y decodificador
Edición no es mostrar - redimensionar una foto de 48MP pertenece en expo-image-manipulator, no en una prop de estilo <Image>
Maps y Skia son contenido rico, no captura - trata esto como dependencias nativas separadas
Relacionado: expo-camera - rendimiento de vista previa | expo-video - ciclo de vida del reproductor
Necesidad Módulo Por qué Visor personalizado, código de barras, linterna expo-cameraPosee la sesión de cámara Adjuntar de una sola vez desde galería expo-image-pickerUI del selector del sistema, sin código de vista previa Overlay de marca en vista previa en vivo expo-cameraCameraView es una vista previa nativa de pantalla completa"Tomar foto" sin UI personalizada launchCameraAsyncDelega en la aplicación de cámara del SO
// src/media/captureChoice.ts
import * as ImagePicker from "expo-image-picker" ;
export async function quickAttachFromGallery () {
const { status } = await ImagePicker. requestMediaLibraryPermissionsAsync ();
if (status !== "granted" ) return null ;
const result = await ImagePicker. launchImageLibraryAsync ({
mediaTypes: [ "images" ],
quality: 0.7 ,
allowsEditing: true ,
});
return result.canceled ? null : result.assets[ 0 ];
}
expo-camera cuando el producto es propietario del encuadre, escaneo o UX de linterna
expo-image-picker cuando el producto solo necesita un URI de archivo de vuelta
Solicita el permiso de cámara solo cuando el usuario toque captura - no en el montaje de pantalla
Relacionado: expo-camera & expo-image-picker - flujos de captura seguros de permisos y memoria
Los feeds, avatares y banners de héroe deben usar expo-image , no Image de React Native. El caché de disco, los placeholders de BlurHash y las claves de reciclado están incorporados.
npx expo install expo-image
// src/media/Avatar.tsx
import { Image } from "expo-image" ;
import { StyleSheet, View } from "react-native" ;
type Props = {
uri : string ;
blurhash ?: string ;
size ?: number ;
};
export function Avatar ({ uri , blurhash , size = 48 } : Props ) {
return (
< View style = {[styles.ring, { width: size, height: size, borderRadius: size / 2 }]}>
< Image
source = {{ uri }}
placeholder = {blurhash ? { blurhash } : undefined }
contentFit = "cover"
transition = { 200 }
cachePolicy = "memory-disk"
style = {{ width: size, height: size, borderRadius: size / 2 }}
/>
</ View >
);
}
const styles = StyleSheet. create ({
ring: { overflow: "hidden" , backgroundColor: "#e5e7eb" },
});
cachePolicy="memory-disk" es adecuado para avatares mostrados repetidamente en pantallas
placeholder + transition elimina parpadeo cuando las URLs se resuelven después de la búsqueda de API
Empareja con recyclingKey en filas de FlashList - ver expo-image
Las pantallas nuevas deben usar expo-video con useVideoPlayer y VideoView. El reproductor es un SharedObject nativo con ciclo de vida explícito.
npx expo install expo-video
// src/media/InlineClip.tsx
import { useVideoPlayer, VideoView } from "expo-video" ;
import { StyleSheet, View } from "react-native" ;
type Props = { uri : string };
export function InlineClip ({ uri } : Props ) {
const player = useVideoPlayer (uri, ( p ) => {
p.loop = true ;
p.muted = true ;
p. play ();
});
return (
< View style = {styles.box}>
< VideoView style = {styles.video} player = {player} contentFit = "cover" />
</ View >
);
}
const styles = StyleSheet. create ({
box: { width: "100%" , aspectRatio: 16 / 9 , backgroundColor: "#000" },
video: { flex: 1 },
});
useVideoPlayer crea y dispone el reproductor cuando el componente se desmonta
Silencia clips de reproducción automática en feeds - el audio inesperado es una queja principal de revisión de tienda
El audio de fondo y PiP requieren flags de plugin de configuración - ver expo-video
Redimensiona y comprime en el dispositivo antes de previsualizar o cargar. La salida de captura de resolución completa no debe aterrizar en el state de React.
npx expo install expo-image-manipulator
// src/media/prepareUpload.ts
import * as ImageManipulator from "expo-image-manipulator" ;
export async function preparePhotoForUpload ( localUri : string ) {
const result = await ImageManipulator. manipulateAsync (
localUri,
[{ resize: { width: 1920 } }],
{ compress: 0.75 , format: ImageManipulator.SaveFormat. JPEG }
);
return result.uri;
}
Riesgoso: setState(fullResUri) - <Image source={{ uri }} /> en FlatList
Más seguro: manipulateAsync - ancho máximo 1280-1920 - cargar archivo temporal
JPEG 0.7-0.8 suele estar bien para fotos de campo y fotos de perfil
Ejecuta manipulator fuera de la ruta crítica de tap cuando sea posible - muestra un spinner en "Usar foto"
El trimming de video, filtros y líneas de tiempo multi-clip pertenecen en pipelines nativos o en la nube , no en bucles de JS
Los flujos de QR y código de barras necesitan una vista previa en vivo o el escáner de plataforma - no image-picker.
// src/media/ScanGate.tsx
import { CameraView } from "expo-camera" ;
import { useCallback, useRef } from "react" ;
import { StyleSheet, Text, View } from "react-native" ;
type Props = { onCode : ( data : string ) => void };
export function ScanGate ({ onCode } : Props ) {
const lastScan = useRef ( 0 );
const handleScan = useCallback (
({ data } : { data : string }) => {
const now = Date. now ();
if (now - lastScan.current < 1500 ) return ;
lastScan.current = now;
onCode (data);
},
[onCode]
);
return (
< View style = {styles.root}>
< CameraView
style = {styles.camera}
barcodeScannerSettings = {{ barcodeTypes: [ "qr" , "code128" ] }}
onBarcodeScanned = {handleScan}
/>
< Text style = {styles.hint}>Alinea el código dentro del marco</ Text >
</ View >
);
}
const styles = StyleSheet. create ({
root: { flex: 1 },
camera: { flex: 1 },
hint: { position: "absolute" , bottom: 48 , alignSelf: "center" , color: "#fff" },
});
Debounce onBarcodeScanned - se dispara muchas veces por segundo
Valida la carga útil con Zod antes de la navegación de enlace profundo
El ajuste de linterna y vista previa son temas de libro de recetas en expo-camera
expo-camera permite una sesión de vista previa activa. Desmonta CameraView cuando la pantalla pierde el foco.
// src/media/FocusedCamera.tsx
import { CameraView, useCameraPermissions } from "expo-camera" ;
import { useIsFocused } from "@react-navigation/native" ;
import { StyleSheet, Text, View } from "react-native" ;
export function FocusedCamera () {
const isFocused = useIsFocused ();
const [ permission , requestPermission ] = useCameraPermissions ();
if ( ! permission?.granted) {
return (
< View style = {styles.gate}>
< Text onPress = {requestPermission}>Permitir cámara</ Text >
</ View >
);
}
if ( ! isFocused) {
return < View style = {styles.placeholder} />;
}
return < CameraView style = {styles.camera} facing = "back" />;
}
const styles = StyleSheet. create ({
gate: { flex: 1 , justifyContent: "center" , alignItems: "center" },
placeholder: { flex: 1 , backgroundColor: "#000" },
camera: { flex: 1 },
});
Los navegadores de tabulación mantienen las pantallas montadas - useIsFocused previene que una pestaña oculta sostengo la cámara
En iOS, active={false} puede pausar la sesión sin desmontar cuando es compatible
Nunca apiles dos instancias de CameraView en el mismo árbol de rutas
Después de capturar, escribe el archivo en la caché de disco de expo-image para que las filas de listas se carguen instantáneamente sin re-decodificar desde una ruta temporal.
// src/media/seedAvatarCache.ts
import { Image } from "expo-image" ;
export async function seedAvatarCache ( localUri : string , userId : string ) {
const cacheKey = `avatar:${ userId }` ;
await Image. writeToCacheAsync (localUri, cacheKey);
return { uri: localUri, cacheKey };
}
< Image
source = {{ uri: localUri, cacheKey: `avatar:${ userId }` }}
contentFit = "cover"
style = {{ width: 48 , height: 48 , borderRadius: 24 }}
/>
writeToCacheAsync es para archivos que ya tienes - salida de picker, descargas, resultados de manipulator
Reutiliza el mismo cacheKey en source al renderizar más tarde
Las recetas de caché completas viven en expo-image
expo-video puede almacenar en búfer una segunda fuente antes de que se adjunte a un VideoView - útil para historias y lectores de lecciones.
// src/media/DualPlayerSwap.tsx
import { useCallback, useState } from "react" ;
import { Pressable, Text, View } from "react-native" ;
import { useVideoPlayer, VideoView, VideoSource } from "expo-video" ;
const clipA : VideoSource =
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4" ;
const clipB : VideoSource =
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4" ;
export function DualPlayerSwap () {
const playerA = useVideoPlayer (clipA, ( p ) => p. play ());
const playerB = useVideoPlayer (clipB);
const [ current , setCurrent ] = useState (playerA);
const swap = useCallback (() => {
current. pause ();
if (current === playerA) {
playerB. play ();
setCurrent (playerB);
} else {
playerA. play ();
setCurrent (playerA);
}
}, [current, playerA, playerB]);
return (
< View style = {{ flex: 1 }}>
< VideoView player = {current} style = {{ flex: 1 }} contentFit = "contain" />
< Pressable onPress = {swap} style = {{ padding: 16 }}>
< Text >Siguiente clip</ Text >
</ Pressable >
</ View >
);
}
Crea useVideoPlayer(secondSource) temprano para que los búferes se llenen mientras se reproduce el primer clip
Pausa el reproductor saliente antes de cambiar - evita dos sesiones de audio
En Android, un VideoView por instancia de reproductor - no montes dos vistas en el mismo reproductor
react-native-maps y @shopify/react-native-skia son librerías nativas de comunidad . Requieren una compilación de desarrollo, plugins de configuración y sus propios presupuestos de rendimiento.
npx expo install expo-dev-client react-native-maps @shopify/react-native-skia
react-native-maps - MapView, markers, tiles, clustering
react-native-skia - Canvas, charts, GPU paths, custom shaders
Preocupación Maps Skia Costo principal Búsquedas de mosaicos, recuento de marcadores Llamadas de dibujo, complejidad de ruta Offline UrlTile + mosaicos agrupados o en cachéActivos pre-renderizados o datos locales Expo Go No soportado No soportado Plugin de configuración Claves de API de Google Maps Generalmente ninguna; verifica New Arch
No incrustes un mapa completo dentro de cada fila de lista - usa instantáneas de mapa estáticas o una pantalla de detalle
Skia excela en gráficos y medidores animados donde cientos de vistas de RN ahogarían el bridge
Inmersiones profundas: react-native-maps | react-native-skia
Necesidad del producto Comienza aquí Evita Avatar de cámara o galería expo-camera & image-picker base64 crudo en state Cuadrícula de imágenes de producto expo-image + cachePolicyRN Image sin caché Video de entrenamiento con PiP expo-video + plugin de configuraciónWebView <video> Control QR expo-camera código de barrasAnálisis de capturas de pantalla Mapa de entrega react-native-mapsIncrustar WebView de mapa Gráfico de anillo KPI en vivo react-native-skia60 Views animadas
¿Debo seguir usando expo-av?
Prefiere expo-video y expo-audio para nuevo trabajo de Expo SDK 57.
Migra componentes Video heredados cuando toques la pantalla - no mezcles reproductores en una ruta.
¿Cuándo es aceptable React Native Image?
Activos agrupados pequeños a través de require() en pantallas estáticas.
Para URLs remotas en listas o perfiles, expo-image es el predeterminado en SDK 57.
¿Funcionan los paquetes media en Expo Go?
expo-image , expo-camera , y expo-video se ejecutan en Expo Go en dispositivos compatibles.
react-native-maps y Skia necesitan una compilación de desarrollo .
¿Cuál es la causa OOM de media más grande?
Sostener URIs de captura de resolución completa en state de React mientras renderizas vistas previas en listas. Reduce primero.
¿Cómo instalo el trío principal?
npx expo install expo-image expo-camera expo-video
Usa npx expo install para que las versiones coincidan con los binarios nativos de SDK 57.
Versiones de stack: Esta página fue escrita para React 19.2.3 , React Native 0.86.0 , y Expo SDK 57 (expo ~57.0.4).