Pular para o conteúdo principal

Permitir Vídeo no Promoter

Ticket: PROMOTER-432 > Última atualização: 30 de julho de 2024

Visão Geral

Implementação de funcionalidade para permitir a captura, armazenamento e sincronização de vídeos no aplicativo Promoter, possibilitando que os usuários registrem vídeos durante as atividades de campo, tanto para o cenário "antes" quanto para o cenário "depois" das atividades. A solução contempla todo o fluxo desde a captura do vídeo até o envio para o servidor, incluindo o armazenamento offline e a sincronização posterior.

Tipos de Mídia Suportados

TipoFormatoDescrição
ImagemJPEGFotos da fachada, antes e depois
VídeoMP4Vídeos do antes e depois das atividades

Configuração de Campos

Campos na Configuração (Settings)

CampoDescriçãoTabelaUso
UTILIZAVIDEOHabilita uso de vídeo no aplicativoSettingsDefine se o recurso de vídeo está disponível

Arquivos Modificados

ArquivoLocalizaçãoResponsabilidade
attachments.tsdatabase/schemasSchema para armazenamento de vídeos
records.tsdatabase/schemasSchema para registros com vídeos
GetSettingsServiceResponseDTO.tsdtos/UserInterface para configuração de vídeo
ActivityType.tsinterfacesInterfaces para tipos de atividade
VideoCard/index.tsxscreens/ActivityNote/componentsComponente para exibição de vídeos
VideoModal/index.tsxscreens/ActivityNote/componentsModal para reprodução de vídeos
checkinTabController.tsscreens/ActivityNote/subscreens/CheckinControlador para captura de vídeos
index.tsx (Checkin)screens/ActivityNote/subscreens/CheckinInterface para captura de vídeos
index.tsx (Checkout)screens/ActivityNote/subscreens/CheckoutInterface para finalização com vídeos
ShareBottomSheet/index.tsxscreens/Records/components/RecordOfflineCardCompartilhamento de vídeos
index.tsx (ServiceReport)screens/ServiceReportExibição de vídeos em relatórios
sendVideoAttachment.tsservices/ActivityServiço para envio de vídeos
convertToSend.tsservices/backgroundTasksConversão de vídeos para envio
downloadRecordVideoService.tsservices/RecordsServiço para download de vídeos
index.ts (syncService)services/syncServiceSincronização de vídeos
settingsSlice.tsstore/settingsRedux para configuração de vídeos
settingsTypes.tsstore/settingsTipos para configuração de vídeos
getVideoFilePathFromBase64.tsutilsUtilitário para manipulação de vídeos

Fluxo de Funcionamento

Fluxograma do Processo

Componentes Principais

1. Captura de Vídeo

A captura de vídeo é realizada através do componente Camera da biblioteca react-native-vision-camera, que permite gravar vídeos com controle de qualidade e formato. O processo é gerenciado pelo checkinTabController.ts, que controla o início e fim da gravação, bem como o processamento do vídeo capturado.

// Exemplo de código para iniciar gravação
const handleRecordVideo = async () => {
try {
setIsRecording(true);
const videoId = generateUUID();

await cameraRef.current.startRecording({
fileType: Platform.OS === "ios" ? "mov" : "mp4",
flash: "off",
onRecordingFinished: async (video) => {
// Processamento do vídeo após finalização
const base64 = await FileSystem.readAsStringAsync(video.path, {
encoding: FileSystem.EncodingType.Base64,
});

// Salvar vídeo localmente
savePhotoToDatabase(videoId, base64, true);

// Atualizar estado do aplicativo
dispatch(
setCheckin({
attachments: [
...checkin?.attachments,
{
isNew: true,
type: currentType,
value: value.toString(),
createdAt: new Date(),
isLoading: "success",
id: videoId,
isVideo: true,
},
],
})
);
},
});
} catch (error) {
console.error("Erro ao iniciar gravação:", error);
}
};

2. Armazenamento Local

Os vídeos são armazenados localmente em formato Base64 utilizando o schema attachmentImage, que foi adaptado para suportar vídeos através da adição do campo ISVIDEO:

export const attachmentImage: Realm.ObjectSchema = {
name: "attachmentImage",
primaryKey: "ID",
properties: {
ID: "string",
BASE64: "string",
ISVIDEO: "bool",
},
};

3. Sincronização

A sincronização dos vídeos é realizada pelo serviço syncService, que foi adaptado para identificar e enviar vídeos para o servidor:

// Trecho do código de sincronização de vídeos
if (
response &&
response.data?.NUPRM &&
((VIDEOSANTES && VIDEOSANTES.length > 0) ||
(VIDEOSDEPOIS && VIDEOSDEPOIS.length > 0))
) {
// Processar vídeos do "antes"
for (const objImage of VIDEOSANTES) {
const media = getCurrentImageFromCache(objImage.id);
const path = await getVideoFilePathFromBase64(
media.BASE64,
objImage.id,
"before"
);

// Criar FormData para upload
const formData = new FormData();
formData.append("file", {
uri: path,
name: `video_antes_${Date.now()}.mp4`,
type: "video/mp4",
} as any);

// Enviar para o servidor
await uploadVideoAttachment(response.data?.NUPRM, formData);
}

// Processar vídeos do "depois" (código similar)
}

4. Exibição

A exibição dos vídeos é realizada através dos componentes VideoCard e VideoModal, que permitem visualizar miniaturas dos vídeos e reproduzi-los em tela cheia:

// VideoCard - Miniatura do vídeo
export const VideoCard = ({ base64Uri, id, onPress, currentType }) => {
const [videoPath, setVideoPath] = useState("");

useEffect(() => {
let active = true;
(async () => {
if (base64Uri?.startsWith("data:video/mp4;base64,")) {
const base64 = base64Uri.replace("data:video/mp4;base64,", "");
const path = await getVideoFilePathFromBase64(base64, id, currentType);
if (active) setVideoPath(path);
} else if (base64Uri) {
setVideoPath(base64Uri);
}
})();

return () => {
active = false;
};
}, [base64Uri, id]);

return (
<TouchableOpacity onPress={() => onPress(videoPath)} activeOpacity={0.8}>
<View
style={
{
/* estilos */
}
}
>
<Video source={{ uri: videoPath }} paused resizeMode="cover" />
</View>
<View
style={
{
/* estilos */
}
}
>
<Play color="white" weight="bold" size={20} />
</View>
</TouchableOpacity>
);
};

Configuração do Recurso

Habilitando o Recurso

O recurso de vídeo pode ser habilitado ou desabilitado através da configuração UTILIZAVIDEO no painel administrativo. Quando habilitado, a interface do aplicativo exibirá a opção de gravação de vídeo em vez da captura de fotos.

Configuração no Backend

No backend, é necessário garantir que o servidor esteja configurado para receber e processar arquivos de vídeo. A API /note/${NUPRM}/saveVideo foi implementada para receber os vídeos enviados pelo aplicativo.

Permissões Necessárias

O aplicativo requer as seguintes permissões para o funcionamento correto da funcionalidade de vídeo:

  • Android:

    "permissions": [
    "android.permission.CAMERA",
    "android.permission.RECORD_AUDIO"
    ]
  • iOS:

    <key>NSCameraUsageDescription</key>
    <string>Vidya Promoter necessita de acesso a sua camera.</string>

Implementação Técnica

Modificações no Schema do Banco de Dados

O banco de dados local foi adaptado para suportar o armazenamento de vídeos:

  1. Schema de Anexos:

    export const attachmentImage: Realm.ObjectSchema = {
    name: "attachmentImage",
    primaryKey: "ID",
    properties: {
    ID: "string",
    BASE64: "string",
    ISVIDEO: "bool", // Novo campo para identificar vídeos
    },
    };
  2. Schema de Registros:

    export const recordVideosData: Realm.ObjectSchema = {
    name: "recordVideosData",
    properties: {
    NUVIDEO: "string?",
    SIZE: "double?",
    FILENAME: "string?",
    TYPE: "string?",
    },
    };

Modificações na Interface

A interface do usuário foi adaptada para suportar a captura e visualização de vídeos:

  1. Botão de Captura:

    <Pressable
    onPress={() => controller.getImageFromCamera("before")}
    w={10}
    h={10}
    rounded="full"
    alignItems="center"
    justifyContent="center"
    bgColor="#444444"
    >
    {UTILIZAVIDEO === "S" ? (
    <VideoCamera color="white" weight="bold" size={20} />
    ) : (
    <Camera color="white" weight="bold" size={20} />
    )}
    </Pressable>
  2. Indicador de Gravação:

    {
    isRecording && (
    <Animated.View style={[styles.recordingIndicator, animatedStyle]}>
    <Text style={styles.recordingText}>{formatTime(elapsedTime)}</Text>
    </Animated.View>
    );
    }

Serviços de API

Foram implementados serviços para envio e download de vídeos:

  1. Envio de Vídeos:

    const uploadVideoAttachment = async (NUPRM: string, formData: FormData) => {
    try {
    const response = await api.post(`/note/${NUPRM}/saveVideo`, formData);
    return response;
    } catch (error) {
    console.log("❌ Erro ao enviar vídeo:", error);
    throw error;
    }
    };
  2. Download de Vídeos:

    const downloadRecordVideoService = async (
    NUPRM: string,
    body: { FILENAME: string; NUVIDEO: string }
    ) => {
    try {
    const response = await api.post(`/note/${NUPRM}/downloadVideo`, body, {
    responseType: "arraybuffer",
    });
    return response;
    } catch (error) {
    console.log("erro no downloadRecordVideoService", error);
    }
    };

Considerações de Performance

Otimização de Armazenamento

Para otimizar o armazenamento de vídeos, foram implementadas as seguintes estratégias:

  1. Compressão de Vídeo: Os vídeos são comprimidos antes do armazenamento local.
  2. Limpeza de Cache: Os arquivos temporários são removidos após o envio bem-sucedido para o servidor.

Otimização de Rede

Para otimizar o uso de rede durante a sincronização, foram implementadas as seguintes estratégias:

  1. Sincronização Seletiva: Apenas os vídeos não sincronizados são enviados para o servidor.
  2. Retry Mechanism: Em caso de falha no envio, o sistema tenta novamente automaticamente.

Limitações e Considerações Futuras

Limitações Atuais

  1. Tamanho do Vídeo: Não há limite explícito para o tamanho dos vídeos, o que pode causar problemas de armazenamento.
  2. Duração da Gravação: Não há limite para a duração da gravação, o que pode resultar em arquivos muito grandes.

Melhorias Futuras

  1. Limite de Duração: Implementar limite de duração para gravações de vídeo.
  2. Compressão Avançada: Implementar algoritmos de compressão mais eficientes.
  3. Streaming de Vídeo: Implementar streaming para visualização de vídeos grandes sem necessidade de download completo.

Exemplos Práticos

Cenário: Registro de Atividade com Vídeo

  1. Configuração:

    • UTILIZAVIDEO = 'S'
  2. Fluxo do Usuário:

    • Usuário inicia um novo registro de atividade
    • Seleciona a opção "Registro Antes"
    • Toca no botão de vídeo para iniciar a gravação
    • Grava o cenário atual
    • Finaliza a gravação
    • O vídeo é processado e exibido como miniatura
    • Usuário pode visualizar o vídeo tocando na miniatura
    • Após concluir a atividade, repete o processo para "Registro Depois"
    • Finaliza o registro e o sistema sincroniza os vídeos com o servidor quando houver conexão

Exemplo de Código para Reprodução de Vídeo

export default function VideoModal({
visible,
onClose,
videoUri,
handleDelete,
}) {
const [paused, setPaused] = useState(false);

return (
<Modal visible={visible} transparent animationType="fade">
<Pressable
style={{ flex: 1, backgroundColor: "#000" }}
onPress={() => setPaused(!paused)}
>
<Video
source={{ uri: videoUri }}
style={{ flex: 1 }}
resizeMode="contain"
paused={paused}
controls
ignoreSilentSwitch="ignore"
onEnd={() => onClose()}
/>

{/* Botões de controle */}
<TouchableOpacity
style={
{
/* estilos */
}
}
onPress={onClose}
/>

{handleDelete && (
<TouchableOpacity
style={
{
/* estilos */
}
}
onPress={() => {
onClose();
showWarningOrConfirmationModal({
title: "Atenção!",
type: "error",
subtitle: "Tem certeza que quer deletar esse video?",
onConfirm: handleDelete,
});
}}
>
<Feather name="trash-2" size={24} color="#fff" />
</TouchableOpacity>
)}
</Pressable>
</Modal>
);
}

Observações

  • A implementação de vídeo é compatível com dispositivos Android e iOS.
  • O recurso pode ser habilitado ou desabilitado remotamente através da configuração UTILIZAVIDEO.
  • Os vídeos são armazenados localmente e sincronizados com o servidor quando houver conexão disponível.
  • A interface do usuário adapta-se automaticamente para exibir a opção de vídeo ou foto, dependendo da configuração.