Noções Básicas de Mídia
10 exemplos para você começar com a seleção de módulos de mídia - 7 básicos e 3 intermediários. A mídia móvel consome muita memória e é sensível a permissões; escolha o menor módulo que atenda ao requisito do produto.
Busque em todas as páginas da documentação
10 exemplos para você começar com a seleção de módulos de mídia - 7 básicos e 3 intermediários. A mídia móvel consome muita memória e é sensível a permissões; escolha o menor módulo que atenda ao requisito do produto.
Crie um aplicativo Expo SDK 57 e instale os pacotes de mídia principais nos quais esta seção se baseia:
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"
}
}Ferramentas: Estes exemplos visam Expo SDK 57 (
expo~57.0.4), React Native 0.86.0 e React 19.2.3.
Antes de importar um pacote, classifique a tela. Captura, reprodução e edição têm pilhas nativas e perfis de memória diferentes.
Captura → hardware da câmera, permissões, URIs de arquivos temporários
Reprodução → decodificadores, buffer, sessão de áudio, modos de segundo plano
Edição → transformações de CPU/GPU, muitas vezes bloqueando, a menos que descarregadas
Exibição → visualizações de imagem, cache, reciclagem em 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 novos feeds
},
playback: {
video: "expo-video",
audioOnly: "expo-audio",
},
edit: {
resizeCompress: "expo-image-manipulator",
heavyVideo: "native pipeline or cloud transcode",
},
} as const;expo-image-manipulator, não a uma prop de estilo <Image>Relacionado: expo-camera - desempenho da pré-visualização | expo-video - ciclo de vida do player
| Necessidade | Módulo | Por quê |
|---|---|---|
| Visor personalizado, código de barras, lanterna | expo-camera | Possui a sessão da câmera |
| Seleção única da galeria | expo-image-picker | UI do seletor do sistema, sem código de pré-visualização |
| Overlay de marca em pré-visualização ao vivo | expo-camera | CameraView é uma pré-visualização nativa em tela cheia |
| "Tirar foto" sem UI personalizada | launchCameraAsync | Delega para o aplicativo de câmera do 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 quando o produto é responsável pelo enquadramento, digitalização ou UX da lanternaexpo-image-picker quando o produto só precisa de um URI de arquivo de voltaRelacionado: expo-camera & expo-image-picker - permissões e fluxos de captura seguros em memória
Feeds, avatares e banners principais devem usar expo-image, não o Image do React Native. Cache em disco, placeholders BlurHash e chaves de reciclagem são integrados.
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" é adequado para avatares exibidos repetidamente em várias telasplaceholder + transition remove o piscar quando as URLs são resolvidas após a busca da APIrecyclingKey em linhas FlashList - veja expo-imageNovas telas devem usar expo-video com useVideoPlayer e VideoView. O player é um SharedObject nativo com 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 cria e descarta o player quando o componente é desmontadoRedimensione e comprima no dispositivo antes da pré-visualização ou upload. A saída da câmera em resolução total não deve ir para o estado do 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;
}Arriscado: setState(fullResUri) → <Image source={{ uri }} /> em FlatList
Mais seguro: manipulateAsync → largura máxima 1280–1920 → envia arquivo temporárioFluxos de QR code e código de barras precisam de uma pré-visualização ao vivo ou do scanner da plataforma - não do 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}>Alinhe o código dentro do quadro</Text>
</View>
);
}
const styles = StyleSheet.create({
root: { flex: 1 },
camera: { flex: 1 },
hint: { position: "absolute", bottom: 48, alignSelf: "center", color: "#fff" },
});onBarcodeScanned - ele dispara muitas vezes por segundoexpo-camera permite uma sessão de pré-visualização ativa. Desmonte CameraView quando a tela perder o 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âmera</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 },
});useIsFocused impede que uma aba oculta mantenha a câmera ativaactive={false} pode pausar a sessão sem desmontar quando suportadoCameraView na mesma árvore de rotasApós a captura, grave o arquivo no cache em disco do expo-image para que as linhas da lista carreguem instantaneamente sem redecodificar de um caminho temporário.
// 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 é para arquivos que você já possui - saída do seletor, downloads, resultados do manipuladorcacheKey em source ao renderizar posteriormenteexpo-video pode bufferizar uma segunda fonte antes que ela seja anexada a um VideoView - útil para stories e players de lições.
// 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>Próximo clipe</Text>
</Pressable>
</View>
);
}useVideoPlayer(secondSource) cedo para que os buffers encham enquanto o primeiro clipe tocaVideoView por instância de player - não monte duas visualizações no mesmo playerreact-native-maps e @shopify/react-native-skia são bibliotecas nativas da comunidade. Eles exigem um build de desenvolvimento, plugins de configuração e seus próprios orçamentos de desempenho.
npx expo install expo-dev-client react-native-maps @shopify/react-native-skiareact-native-maps → MapView, marcadores, tiles, clustering
react-native-skia → Canvas, gráficos, caminhos de GPU, shaders personalizados| Preocupação | Mapas | Skia |
|---|---|---|
| Custo principal | Buscas de tiles, contagem de marcadores | Chamadas de desenho, complexidade do caminho |
| Offline | UrlTile + tiles empacotados ou em cache | Assets pré-renderizados ou dados locais |
| Expo Go | Não suportado | Não suportado |
| Plugin de configuração | Chaves da API do Google Maps | Geralmente nenhum; verifique a Nova Arquitetura |
| Necessidade do produto | Comece aqui | Evite |
|---|---|---|
| Avatar da câmera ou galeria | expo-camera & image-picker | Base64 bruto no estado |
| Grade de imagens do produto | expo-image + cachePolicy | RN Image sem cache |
| Vídeo de treinamento com PiP | expo-video + plugin de configuração | WebView <video> |
| Check-in QR code | expo-camera barcode | Análise de capturas de tela |
| Mapa de entrega | react-native-maps | Incorporação de WebView de mapa |
| Gráfico de anel de KPI ao vivo | react-native-skia | 60 Views animados |
expo-video e expo-audio para novo trabalho no Expo SDK 57.Video legados quando tocar na tela - não misture players em uma rota.require() em telas estáticas.expo-image é o padrão no SDK 57.expo-image, expo-camera e expo-video rodam no Expo Go em dispositivos suportados.react-native-maps e Skia precisam de um build de desenvolvimento.npx expo install expo-image expo-camera expo-videoUse npx expo install para que as versões correspondam aos binários nativos do SDK 57.
writeToCacheAsyncVersões da Pilha: Esta página foi escrita para React 19.2.3, React Native 0.86.0 e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026