Introdução à API ARCore Recording and Playback

1. Introdução

Salvar uma experiência de RA em um arquivo MP4 e reproduzi-lo no arquivo MP4 pode ser útil para os desenvolvedores de apps e os usuários finais.

Depurar e testar novos recursos do seu computador

O uso mais simples da API ARCore Record & Playback é para desenvolvedores. Os dias em que você precisava criar e executar o app em um dispositivo de teste, desconectar o cabo USB e ficar andando para testar uma pequena mudança no código ficaram para trás. Agora você só precisa gravar um MP4 no ambiente de teste com o movimento esperado do smartphone e testar diretamente da sua mesa.

Gravar e reproduzir em dispositivos diferentes

Com as APIs Recording and Playback, é possível que um usuário grave uma sessão usando um dispositivo e o outro reproduzir a mesma sessão em um dispositivo diferente. É possível compartilhar uma experiência de RA com outro usuário. Há muitas possibilidades.

Esta é a primeira vez que você faz um app com o ARCore?

Não. Sim.

Como você usará este codelab?

Apenas leitura Leitura e exercícios

O que você criará

Neste codelab, você usará a API Recording & Playback para criar um app que registre uma experiência de RA em um arquivo MP4 e reproduza a experiência no mesmo arquivo. Você aprenderá:

  • Como usar a API Recording para salvar uma sessão de RA em um arquivo MP4.
  • Como usar a API Playback para reproduzir uma sessão de RA de um arquivo MP4.
  • Como gravar uma sessão de RA em um dispositivo e reproduzi-la em outro.

Pré-requisitos

Neste codelab, você modificará o app Hello AR Java, que é criado com o SDK do ARCore Android. Você precisará de hardware e software específicos para acompanhar.

Requisitos de hardware

Requisitos de software

Você também precisa ter um conhecimento básico sobre o ARCore para conseguir os melhores resultados.

2. Configurar seu ambiente de desenvolvimento

Comece configurando seu ambiente de desenvolvimento.

Fazer o download do SDK do ARCore Android

Clique em para fazer o download do SDK.

Descompactar SDK do ARCore Android

Após fazer o download do SDK do Android na sua máquina, descompacte o arquivo e navegue até o diretório arcore-android-sdk-1.24/samples/hello_ar_java. Este é o diretório raiz do app com que você vai trabalhar.

hello-ar-java-extracted

Carregar o Hello AR Java no Android Studio

Inicie o Android Studio e clique em Abrir um projeto do Android Studio existente.

android-studio-open-projects

Na caixa de diálogo exibida, selecione arcore-android-sdk-1.24/samples/hello_ar_java e clique em Abrir.

Aguarde até o Android Studio terminar de sincronizar o projeto. Se houver um componente ausente, a importação do projeto poderá falhar com mensagens de erro. Corrija esses problemas antes de continuar.

Executar o aplicativo de amostra

  1. Conecte um dispositivo compatível com o ARCore à sua máquina de desenvolvimento.
  2. Se o dispositivo for reconhecido corretamente, você verá o nome do dispositivo no Android Studio. android-studio-pixel-5.png
  3. Clique no botão "Executar" ou selecione Executar > Executar "app" para que o Android Studio instale e inicie o app no dispositivo. android-studio-run-button.png
  4. Você verá uma solicitação de permissão para tirar fotos e gravar vídeos. Selecione Durante o uso deste app para conceder permissões de câmera ao app. Você verá o ambiente real na tela do dispositivo. hello-ar-java-permission
  5. Mova o dispositivo horizontalmente para procurar planos.
  6. Uma grade branca aparecerá quando o app detectar um plano. Toque nela para colocar um marcador nesse plano. Posição do Hello AR

O que você fez nesta etapa

  • Configurou o projeto Hello AR Java
  • Criou e executou o app de amostra em um dispositivo compatível com o ARCore

Em seguida, você gravará uma sessão de RA em um arquivo MP4.

3. Gravar uma sessão do ARCore em um arquivo MP4

O recurso de gravação será adicionado nesta etapa. Ela é composta por:

  • Um botão para iniciar ou interromper a gravação.
  • Funções de armazenamento para salvar o arquivo MP4 no dispositivo.
  • Chamadas para iniciar ou interromper a gravação da sessão do ARCore.

Botão "Adicionar IU para gravação"

Antes de implementar a gravação, adicione um botão na IU para que o usuário possa informar ao ARCore quando iniciar ou interromper a gravação.

No painel "Projeto", abra o arquivo app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

Por padrão, o Android Studio usará a visualização de design depois que você abrir o arquivo app/res/layout/activity_main.xml. Clique no botão Código no canto superior direito da guia para alternar para a visualização de código.

swith-to-the-code-view.png

Em activity_main.xml, adicione o seguinte código antes da tag de fechamento para criar o novo botão Gravar e definir o manipulador de eventos como um método chamado onClickRecord():

  <!--
    Add a new "Record" button with those attributes:
        text is "Record",
        onClick event handler is "onClickRecord",
        text color is "red".
  -->
  <Button
      android:id="@+id/record_button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignLeft="@id/surfaceview"
      android:layout_alignBottom="@id/surfaceview"
      android:layout_marginBottom="100dp"
      android:onClick="onClickRecord"
      android:text="Record"
      android:textColor="@android:color/holo_red_light" />

Depois que você adicionar o código acima, um erro poderá ser exibido temporariamente: Corresponding method handler 'public void onClickRecord(android.view.View)' not found". Isso já é esperado. Para resolver o erro, crie a função onClickRecord() nas próximas etapas.

Mudar o texto no botão com base no estado

Na verdade, o botão Gravar processa a gravação e a interrupção. Quando o app não estiver gravando dados, a palavra "Gravar" será exibida. Quando o app estiver gravando dados, o botão precisará mudar para exibir a palavra "Interromper".

Para oferecer ao botão essa funcionalidade, o app precisa conhecer o estado atual dele. O código a seguir cria um novo tipo enumerado chamado AppState para representar o estado de trabalho do app e rastrear alterações específicas de estado através de uma variável de membro particular chamada appState. Adicione-o a HelloArActivity.java, no início da classe HelloArActivity.

  // Represents the app's working state.
  public enum AppState {
    Idle,
    Recording
  }

  // Tracks app's specific state changes.
  private AppState appState = AppState.Idle;

Agora que você pode acompanhar o estado interno do app, crie uma função chamada updateRecordButton(), que muda o texto do botão com base no estado atual do app. Adicione o seguinte código na classe HelloArActivity no HelloArActivity.java.

// Add imports to the beginning of the file.
import android.widget.Button;

  // Update the "Record" button based on app's internal state.
  private void updateRecordButton() {
    View buttonView = findViewById(R.id.record_button);
    Button button = (Button) buttonView;

    switch (appState) {
      case Idle:
        button.setText("Record");
        break;
      case Recording:
        button.setText("Stop");
        break;
    }
  }

Em seguida, crie o método onClickRecord() que verifica o estado do app, muda para o próximo e chama updateRecordButton() para mudar a IU do botão. Adicione o seguinte código na classe HelloArActivity no HelloArActivity.java.

  // Handle the "Record" button click event.
  public void onClickRecord(View view) {
    Log.d(TAG, "onClickRecord");

    // Check the app's internal state and switch to the new state if needed.
    switch (appState) {
        // If the app is not recording, begin recording.
      case Idle: {
        boolean hasStarted = startRecording();
        Log.d(TAG, String.format("onClickRecord start: hasStarted %b", hasStarted));

        if (hasStarted)
          appState = AppState.Recording;

        break;
      }

      // If the app is recording, stop recording.
      case Recording: {
        boolean hasStopped = stopRecording();
        Log.d(TAG, String.format("onClickRecord stop: hasStopped %b", hasStopped));

        if (hasStopped)
          appState = AppState.Idle;

        break;
      }

      default:
        // Do nothing.
        break;
    }

    updateRecordButton();
  }

Ativar o app para começar a gravar

Você só precisa fazer duas coisas para começar a gravar no ARCore:

  1. Especifique o URI do arquivo de gravação em um objeto RecordingConfig.
  2. Chamar session.startRecording com o objeto RecordingConfig

O restante é apenas um código boilerplate: configuração, geração de registros e verificação de precisão.

Crie uma nova função chamada startRecording() que registra dados e os salva em um URI MP4. Adicione o seguinte código na classe HelloArActivity no HelloArActivity.java.

// Add imports to the beginning of the file.
import android.net.Uri;
import com.google.ar.core.RecordingConfig;
import com.google.ar.core.RecordingStatus;
import com.google.ar.core.exceptions.RecordingFailedException;

  private boolean startRecording() {
    Uri mp4FileUri = createMp4File();
    if (mp4FileUri == null)
      return false;

    Log.d(TAG, "startRecording at: " + mp4FileUri);

    pauseARCoreSession();

    // Configure the ARCore session to start recording.
    RecordingConfig recordingConfig = new RecordingConfig(session)
        .setMp4DatasetUri(mp4FileUri)
        .setAutoStopOnPause(true);

    try {
      // Prepare the session for recording, but do not start recording yet.
      session.startRecording(recordingConfig);
    } catch (RecordingFailedException e) {
      Log.e(TAG, "startRecording - Failed to prepare to start recording", e);
      return false;
    }

    boolean canResume = resumeARCoreSession();
    if (!canResume)
      return false;

    // Correctness checking: check the ARCore session's RecordingState.
    RecordingStatus recordingStatus = session.getRecordingStatus();
    Log.d(TAG, String.format("startRecording - recordingStatus %s", recordingStatus));
    return recordingStatus == RecordingStatus.OK;
  }

Para pausar e retomar uma sessão do ARCore com segurança, crie pauseARCoreSession() e resumeARCoreSession() em HelloArActivity.java.

  private void pauseARCoreSession() {
    // Pause the GLSurfaceView so that it doesn't update the ARCore session.
    // Pause the ARCore session so that we can update its configuration.
    // If the GLSurfaceView is not paused,
    //   onDrawFrame() will try to update the ARCore session
    //   while it's paused, resulting in a crash.
    surfaceView.onPause();
    session.pause();
  }

  private boolean resumeARCoreSession() {
    // We must resume the ARCore session before the GLSurfaceView.
    // Otherwise, the GLSurfaceView will try to update the ARCore session.
    try {
      session.resume();
    } catch (CameraNotAvailableException e) {
      Log.e(TAG, "CameraNotAvailableException in resumeARCoreSession", e);
      return false;
    }

    surfaceView.onResume();
    return true;
  }

Ativar o app para interromper a gravação

Crie uma função chamada stopRecording() em HelloArActivity.java para evitar que o app grave novos dados. Essa função chama session.stopRecording() e envia um erro para o registro do console se não for possível interromper a gravação do app.

  private boolean stopRecording() {
    try {
      session.stopRecording();
    } catch (RecordingFailedException e) {
      Log.e(TAG, "stopRecording - Failed to stop recording", e);
      return false;
    }

    // Correctness checking: check if the session stopped recording.
    return session.getRecordingStatus() == RecordingStatus.NONE;
  }

Projetar armazenamento de arquivos usando o armazenamento com escopo do Android 11

As funções relacionadas ao armazenamento deste codelab foram projetadas de acordo com os novos requisitos de armazenamento com escopo do Android 11.

Faça algumas pequenas mudanças no arquivo app/build.gradle para direcioná-lo ao Android 11. No painel "Projeto" do Android Studio, esse arquivo está sob o nó Gradle Scripts, associado ao módulo app.

app-build.gradle.png

Mude compileSdkVersion e targetSdkVersion para 30.

    compileSdkVersion 30
    defaultConfig {
      targetSdkVersion 30
    }

Para gravar, use a API Android MediaStore para criar o arquivo MP4 no diretório "Filme" compartilhado.

Crie uma função chamada createMp4File() em HelloArActivity.java:

// Add imports to the beginning of the file.
import java.text.SimpleDateFormat;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.content.ContentValues;
import java.io.File;
import android.content.CursorLoader;
import android.database.Cursor;
import java.util.Date;

  private final String MP4_VIDEO_MIME_TYPE = "video/mp4";

  private Uri createMp4File() {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
    String mp4FileName = "arcore-" + dateFormat.format(new Date()) + ".mp4";

    ContentResolver resolver = this.getContentResolver();

    Uri videoCollection = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
      videoCollection = MediaStore.Video.Media.getContentUri(
          MediaStore.VOLUME_EXTERNAL_PRIMARY);
    } else {
      videoCollection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    }

    // Create a new Media file record.
    ContentValues newMp4FileDetails = new ContentValues();
    newMp4FileDetails.put(MediaStore.Video.Media.DISPLAY_NAME, mp4FileName);
    newMp4FileDetails.put(MediaStore.Video.Media.MIME_TYPE, MP4_VIDEO_MIME_TYPE);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
      // The Relative_Path column is only available since API Level 29.
      newMp4FileDetails.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES);
    } else {
      // Use the Data column to set path for API Level <= 28.
      File mp4FileDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
      String absoluteMp4FilePath = new File(mp4FileDir, mp4FileName).getAbsolutePath();
      newMp4FileDetails.put(MediaStore.Video.Media.DATA, absoluteMp4FilePath);
    }

    Uri newMp4FileUri = resolver.insert(videoCollection, newMp4FileDetails);

    // Ensure that this file exists and can be written.
    if (newMp4FileUri == null) {
      Log.e(TAG, String.format("Failed to insert Video entity in MediaStore. API Level = %d", Build.VERSION.SDK_INT));
      return null;
    }

    // This call ensures the file exist before we pass it to the ARCore API.
    if (!testFileWriteAccess(newMp4FileUri)) {
      return null;
    }

    Log.d(TAG, String.format("createMp4File = %s, API Level = %d", newMp4FileUri, Build.VERSION.SDK_INT));

    return newMp4FileUri;
  }

  // Test if the file represented by the content Uri can be open with write access.
  private boolean testFileWriteAccess(Uri contentUri) {
    try (java.io.OutputStream mp4File = this.getContentResolver().openOutputStream(contentUri)) {
      Log.d(TAG, String.format("Success in testFileWriteAccess %s", contentUri.toString()));
      return true;
    } catch (java.io.FileNotFoundException e) {
      Log.e(TAG, String.format("FileNotFoundException in testFileWriteAccess %s", contentUri.toString()), e);
    } catch (java.io.IOException e) {
      Log.e(TAG, String.format("IOException in testFileWriteAccess %s", contentUri.toString()), e);
    }

    return false;
  }

Processar permissões de armazenamento

Caso você esteja usando um dispositivo Android 11, comece a testar o código. Para oferecer compatibilidade com o Android 10 ou em dispositivos anteriores, você precisa conceder permissões de armazenamento do app para salvar dados no sistema de arquivos do dispositivo de destino.

Em AndroidManifest.xml, declare que o app precisa de permissões de leitura e gravação de armazenamento antes do Android 11 (API de nível 30).

  <!-- Inside the <manifest> tag, below the existing Camera permission -->
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
      android:maxSdkVersion="29" />

  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
      android:maxSdkVersion="29" />

Adicione uma função auxiliar com o nome checkAndRequestStoragePermission() em HelloArActivity.java para solicitar as permissões WRITE_EXTERNAL_STORAGE durante o tempo de execução.

// Add imports to the beginning of the file.
import android.Manifest;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

  private final int REQUEST_WRITE_EXTERNAL_STORAGE = 1;
  public boolean checkAndRequestStoragePermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
        != PackageManager.PERMISSION_GRANTED) {
      ActivityCompat.requestPermissions(this,
          new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
          REQUEST_WRITE_EXTERNAL_STORAGE);
      return false;
    }

    return true;
  }

Se você estiver usando o nível 29 da API ou anterior, adicione uma verificação de permissões de armazenamento na parte superior do createMp4File() e saia da função antecipadamente se o app não tiver as permissões corretas. A API de nível 30 (Android 11) não requer permissão de armazenamento para acessar arquivos em MediaStore.

  private Uri createMp4File() {
    // Since we use legacy external storage for Android 10,
    // we still need to request for storage permission on Android 10.
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
      if (!checkAndRequestStoragePermission()) {
        Log.i(TAG, String.format(
            "Didn't createMp4File. No storage permission, API Level = %d",
            Build.VERSION.SDK_INT));
        return null;
      }
    }
    // ... omitted code ...
  }

Gravar do dispositivo de destino

É hora de ver o que você criou até agora. Conecte seu dispositivo móvel à máquina de desenvolvimento e clique em Executar no Android Studio.

Você verá o botão vermelho Gravar no canto inferior esquerdo da tela. Ao tocar nele, o texto deve ser alterado para Interromper. Mova o dispositivo para gravar uma sessão e clique no botão Interromper quando quiser concluir a gravação. Um novo arquivo chamado arcore-xxxxxx_xxxxxx.mp4 será salvo no armazenamento externo do dispositivo.

record-button.png

Agora você terá um novo arquivo arcore-xxxxxx_xxxxxx.mp4 no armazenamento externo do seu dispositivo. Em dispositivos Pixel 5, o caminho é /storage/emulated/0/Movies/. O caminho pode ser encontrado na janela do Logcat após o início da gravação.

com.google.ar.core.examples.java.helloar D/HelloArActivity: startRecording at:/storage/emulated/0/Movies/arcore-xxxxxxxx_xxxxxx.mp4
com.google.ar.core.examples.java.helloar D/HelloArActivity: startRecording - RecordingStatus OK

Visualizar a gravação

Você pode usar um app do sistema de arquivos, como o Files do Google, para ver a gravação ou copiá-la na máquina de desenvolvimento. Veja abaixo os dois comandos adb para listar e buscar arquivos no dispositivo Android:

  • adb shell ls '$EXTERNAL_STORAGE/Movies/*' para mostrar os arquivos no diretório "Filmes" no armazenamento externo do dispositivo
  • adb pull /absolute_path_from_previous_adb_shell_ls/arcore-xxxxxxxx_xxxxxx.mp4 para copiar o arquivo do dispositivo para a máquina de desenvolvimento

Este é um exemplo de saída depois do uso destes dois comandos (do macOS):

$ adb shell ls '$EXTERNAL_STORAGE/Movies/*'
/sdcard/Movies/arcore-xxxxxxxx_xxxxxx.mp4

$ adb pull /sdcard/Movies/arcore-xxxxxxxx_xxxxxx.mp4
/sdcard/Movies/arcore-xxxxxxxx_xxxxxx.mp4: ... pulled

O que você fez nesta etapa

  • Adicionou um botão para iniciar e interromper a gravação
  • Implementou funções para iniciar e interromper a gravação
  • Testou o app no dispositivo
  • Copiou o MP4 gravado para a máquina e o verificou

Depois você vai reproduzir uma sessão de RA de um arquivo MP4.

4. Reproduzir uma sessão do ARCore com um arquivo MP4

Agora você tem um botão Gravar e alguns arquivos MP4 com sessões gravadas. Você vai reproduzi-los com a API ARCore Playback.

Botão "Adicionar IU para reprodução"

Antes de implementar a reprodução, adicione um botão na IU para que o usuário possa informar ao ARCore quando a reprodução da sessão deve começar e parar.

No painel Projeto, abra o arquivo app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

Em activity_main.xml, adicione o código abaixo antes da tag de fechamento para criar o novo botão Reproduzir e definir o manipulador de eventos como um método chamado onClickPlayback(). Esse botão é semelhante ao botão Gravar e será exibido no lado direito da tela.

  <!--
    Add a new "Playback" button with those attributes:
        text is "Playback",
        onClick event handler is "onClickPlayback",
        text color is "green".
  -->
  <Button
      android:id="@+id/playback_button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignEnd="@id/surfaceview"
      android:layout_alignBottom="@id/surfaceview"
      android:layout_marginBottom="100dp"
      android:onClick="onClickPlayback"
      android:text="Playback"
      android:textColor="@android:color/holo_green_light" />

Botões de atualização durante a reprodução

Agora o app tem um novo estado chamado Playingback. Atualize o tipo enumerado AppState e todas as funções existentes que utilizam appState como argumento para processar isso.

Adicione Playingback ao tipo enumerado AppState em HelloArActivity.java:

  public enum AppState {
    Idle,
    Recording,
    Playingback // New enum value.
  }

Se o botão Gravar ainda estiver aparecendo na tela durante a reprodução, o usuário poderá clicar nele por acidente. Para evitar que isso aconteça, oculte o botão Gravar durante a reprodução. Dessa forma, não é necessário processar o estado Playingback em onClickRecord().

Modifique a função updateRecordButton() em HelloArActivity.java para ocultar o botão Gravar quando o app estiver no estado Playingback.

  // Update the "Record" button based on app's internal state.
  private void updateRecordButton() {
    View buttonView = findViewById(R.id.record_button);
    Button button = (Button)buttonView;

    switch (appState) {

      // The app is neither recording nor playing back. The "Record" button is visible.
      case Idle:
        button.setText("Record");
        button.setVisibility(View.VISIBLE);
        break;

      // While recording, the "Record" button is visible and says "Stop".
      case Recording:
        button.setText("Stop");
        button.setVisibility(View.VISIBLE);
        break;

      // During playback, the "Record" button is not visible.
      case Playingback:
        button.setVisibility(View.INVISIBLE);
        break;
    }
  }

Da mesma forma, oculte o botão Reproduzir quando o usuário estiver gravando uma sessão. Depois, mude-o para "Interromper" quando o usuário estiver reproduzindo uma sessão. Dessa forma, eles podem parar uma reprodução sem precisar esperar que ela termine por conta própria.

Adicione uma função updatePlaybackButton() em HelloArActivity.java:

  // Update the "Playback" button based on app's internal state.
  private void updatePlaybackButton() {
    View buttonView = findViewById(R.id.playback_button);
    Button button = (Button)buttonView;

    switch (appState) {

      // The app is neither recording nor playing back. The "Playback" button is visible.
      case Idle:
        button.setText("Playback");
        button.setVisibility(View.VISIBLE);
        break;

      // While playing back, the "Playback" button is visible and says "Stop".
      case Playingback:
        button.setText("Stop");
        button.setVisibility(View.VISIBLE);
        break;

      // During recording, the "Playback" button is not visible.
      case Recording:
        button.setVisibility(View.INVISIBLE);
        break;
    }
  }

Por fim, atualize onClickRecord() para chamar updatePlaybackButton(). Adicione a linha a seguir a HelloArActivity.java:

  public void onClickRecord(View view) {
    // ... omitted code ...
    updatePlaybackButton(); // Add this line to the end of the function.
  }

Selecionar um arquivo com o botão "Reproduzir"

Quando tocado, o botão Reproduzir permitirá que o usuário selecione um arquivo para reproduzir. No Android, a seleção de arquivos é processada no seletor de arquivos do sistema em outra atividade. Isso é feito por meio do Storage Access Framework (SAF). Depois que o usuário seleciona um arquivo, o app recebe um callback com o nome onActivityResult(). Você iniciará a reprodução real dentro dessa função de callback.

Em HelloArActivity.java, crie uma função onClickPlayback() para fazer a seleção de arquivos e parar a reprodução.

  // Handle the click event of the "Playback" button.
  public void onClickPlayback(View view) {
    Log.d(TAG, "onClickPlayback");

    switch (appState) {

      // If the app is not playing back, open the file picker.
      case Idle: {
        boolean hasStarted = selectFileToPlayback();
        Log.d(TAG, String.format("onClickPlayback start: selectFileToPlayback %b", hasStarted));
        break;
      }

      // If the app is playing back, stop playing back.
      case Playingback: {
        boolean hasStopped = stopPlayingback();
        Log.d(TAG, String.format("onClickPlayback stop: hasStopped %b", hasStopped));
        break;
      }

      default:
        // Recording - do nothing.
        break;
    }

    // Update the UI for the "Record" and "Playback" buttons.
    updateRecordButton();
    updatePlaybackButton();
  }

Em HelloArActivity.java, crie uma função selectFileToPlayback() que selecione um arquivo do dispositivo. Para selecionar um arquivo do sistema de arquivos do Android, use uma intent ACTION_OPEN_DOCUMENT.

// Add imports to the beginning of the file.
import android.content.Intent;
import android.provider.DocumentsContract;

  private boolean selectFileToPlayback() {
    // Start file selection from Movies directory.
    // Android 10 and above requires VOLUME_EXTERNAL_PRIMARY to write to MediaStore.
    Uri videoCollection;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
      videoCollection = MediaStore.Video.Media.getContentUri(
          MediaStore.VOLUME_EXTERNAL_PRIMARY);
    } else {
      videoCollection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    }

    // Create an Intent to select a file.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

    // Add file filters such as the MIME type, the default directory and the file category.
    intent.setType(MP4_VIDEO_MIME_TYPE); // Only select *.mp4 files
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, videoCollection); // Set default directory
    intent.addCategory(Intent.CATEGORY_OPENABLE); // Must be files that can be opened

    this.startActivityForResult(intent, REQUEST_MP4_SELECTOR);

    return true;
  }

REQUEST_MP4_SELECTOR é uma constante para identificar essa solicitação. É possível defini-la usando qualquer valor de marcador dentro de HelloArActivity em HelloArActivity.java:

  private int REQUEST_MP4_SELECTOR = 1;

Substitua a função onActivityResult() em HelloArActivity.java para processar o callback do seletor de arquivos.

  // Begin playback once the user has selected the file.
  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Check request status. Log an error if the selection fails.
    if (resultCode != android.app.Activity.RESULT_OK || requestCode != REQUEST_MP4_SELECTOR) {
      Log.e(TAG, "onActivityResult select file failed");
      return;
    }

    Uri mp4FileUri = data.getData();
    Log.d(TAG, String.format("onActivityResult result is %s", mp4FileUri));

    // Begin playback.
    startPlayingback(mp4FileUri);
  }

Permitir que o app inicie a reprodução

Uma sessão do ARCore exige três chamadas de API para reproduzir um arquivo MP4:

  1. session.pause()
  2. session.setPlaybackDataset()
  3. session.resume()

Em HelloArActivity.java, crie a função startPlayingback().

// Add imports to the beginning of the file.
import com.google.ar.core.PlaybackStatus;
import com.google.ar.core.exceptions.PlaybackFailedException;

  private boolean startPlayingback(Uri mp4FileUri) {
    if (mp4FileUri == null)
      return false;

    Log.d(TAG, "startPlayingback at:" + mp4FileUri);

    pauseARCoreSession();

    try {
      session.setPlaybackDatasetUri(mp4FileUri);
    } catch (PlaybackFailedException e) {
      Log.e(TAG, "startPlayingback - setPlaybackDataset failed", e);
    }

    // The session's camera texture name becomes invalid when the
    // ARCore session is set to play back.
    // Workaround: Reset the Texture to start Playback
    // so it doesn't crashes with AR_ERROR_TEXTURE_NOT_SET.
    hasSetTextureNames = false;

    boolean canResume = resumeARCoreSession();
    if (!canResume)
      return false;

    PlaybackStatus playbackStatus = session.getPlaybackStatus();
    Log.d(TAG, String.format("startPlayingback - playbackStatus %s", playbackStatus));

    if (playbackStatus != PlaybackStatus.OK) { // Correctness check
      return false;
    }

    appState = AppState.Playingback;
    updateRecordButton();
    updatePlaybackButton();

    return true;
  }

Permitir que o app interrompa a reprodução

Crie uma função com o nome stopPlayingback() em HelloArActivity.java para processar as mudanças de estado do app depois que:

  1. A reprodução de MP4 for interrompida pelo usuário
  2. A reprodução de MP4 for concluída por conta própria

Se o usuário tiver interrompido a reprodução, o app retornará ao mesmo estado em que estava quando o usuário o iniciou pela primeira vez.

  // Stop the current playback, and restore app status to Idle.
  private boolean stopPlayingback() {
    // Correctness check, only stop playing back when the app is playing back.
    if (appState != AppState.Playingback)
      return false;

    pauseARCoreSession();

    // Close the current session and create a new session.
    session.close();
    try {
      session = new Session(this);
    } catch (UnavailableArcoreNotInstalledException
        |UnavailableApkTooOldException
        |UnavailableSdkTooOldException
        |UnavailableDeviceNotCompatibleException e) {
      Log.e(TAG, "Error in return to Idle state. Cannot create new ARCore session", e);
      return false;
    }
    configureSession();

    boolean canResume = resumeARCoreSession();
    if (!canResume)
      return false;

    // A new session will not have a camera texture name.
    // Manually set hasSetTextureNames to false to trigger a reset.
    hasSetTextureNames = false;

    // Reset appState to Idle, and update the "Record" and "Playback" buttons.
    appState = AppState.Idle;
    updateRecordButton();
    updatePlaybackButton();

    return true;
  }

Naturalmente, a reprodução também se interrompe quando o player atinge o fim do arquivo MP4. Quando isso acontecer, stopPlayingback() mudará o estado do app para Idle. Em onDrawFrame(), verifique o PlaybackStatus. Se for FINISHED, chame a função stopPlayingback() na linha de execução de IU.

  public void onDrawFrame(SampleRender render) {
      // ... omitted code ...

      // Insert before this line:
      // frame = session.update();

      // Check the playback status and return early if playback reaches the end.
      if (appState == AppState.Playingback
          && session.getPlaybackStatus() == PlaybackStatus.FINISHED) {
        this.runOnUiThread(this::stopPlayingback);
        return;
      }

      // ... omitted code ...
  }

Assistir do dispositivo de destino

É hora de ver o que você criou até agora. Conecte seu dispositivo móvel à máquina de desenvolvimento e clique em Executar no Android Studio.

Quando o app for iniciado, você verá uma tela com um botão vermelho Gravar à esquerda e um botão verde Reproduzir à direita.

playback-button.png

Toque no botão Reproduzir e selecione um dos arquivos MP4 que você acabou de gravar. Caso não veja nomes de arquivos iniciados por arcore-, talvez seu dispositivo não esteja exibindo a pasta Filmes. Nesse caso, navegue até a pasta Modelo do smartphone > Filmes usando o menu no canto superior esquerdo. Talvez você também precise ativar a opção Mostrar armazenamento interno para ver a pasta do modelo do smartphone.

show-internal-storage-button.png

nativate-to-movies-file-picker.jpg

Toque em um nome de arquivo na tela para selecionar o arquivo MP4. O app reproduzirá o arquivo MP4.

playback-stop-button.png

A diferença entre assistir uma sessão ou um vídeo comum é a possibilidade de interagir com a sessão gravada. Toque em um plano detectado para colocar marcadores na tela.

playback-placement

O que você fez nesta etapa

  • Adicionou um botão para iniciar e interromper a reprodução
  • Implementou uma função para fazer com que o app inicie e interrompa a gravação
  • Reproduziu uma sessão do ARCore gravada anteriormente no dispositivo

5. Gravar mais dados no MP4

Com o ARCore 1.24, é possível gravar mais informações no arquivo MP4. É possível gravar o Pose das posições de objetos de RA e, durante a reprodução, criar os objetos de RA no mesmo local.

Configurar a nova faixa a ser gravada

Defina uma nova faixa com um UUID e uma tag MIME em HelloArActivity.java.

// Add imports to the beginning of the file.
import java.util.UUID;
import com.google.ar.core.Track;

  // Inside the HelloArActiity class.
  private static final UUID ANCHOR_TRACK_ID = UUID.fromString("53069eb5-21ef-4946-b71c-6ac4979216a6");;
  private static final String ANCHOR_TRACK_MIME_TYPE = "application/recording-playback-anchor";

  private boolean startRecording() {
    // ... omitted code ...

    // Insert after line:
    //   pauseARCoreSession();

    // Create a new Track, with an ID and MIME tag.
    Track anchorTrack = new Track(session)
        .setId(ANCHOR_TRACK_ID).
        .setMimeType(ANCHOR_TRACK_MIME_TYPE);
    // ... omitted code ...
  }

Atualize o código de saída para criar o objeto RecordingConfig com uma chamada para addTrack().

  private boolean startRecording() {
    // ... omitted code ...

    // Update the lines below with a call to the addTrack() function:
    //   RecordingConfig recordingConfig = new RecordingConfig(session)
    //    .setMp4DatasetUri(mp4FileUri)
    //    .setAutoStopOnPause(true);

    RecordingConfig recordingConfig = new RecordingConfig(session)
        .setMp4DatasetUri(mp4FileUri)
        .setAutoStopOnPause(true)
        .addTrack(anchorTrack); // add the new track onto the recordingConfig

    // ... omitted code ...
  }

Salvar posição da âncora durante a gravação

Sempre que o usuário tocar em um plano detectado, um marcador de RA será colocado em um Anchor, cuja posição será atualizada pelo ARCore.

Registre a posição de um Anchor no frame em que ele é criado, caso você ainda esteja gravando a sessão do ARCore.

Modifique a função handleTap() em HelloArActivity.java.

// Add imports to the beginning of the file.
import com.google.ar.core.Pose;
import java.nio.FloatBuffer;

  private void handleTap(Frame frame, Camera camera) {
          // ... omitted code ...

          // Insert after line:
          // anchors.add(hit.createAnchor());

          // If the app is recording a session,
          // save the new Anchor pose (relative to the camera)
          // into the ANCHOR_TRACK_ID track.
          if (appState == AppState.Recording) {
            // Get the pose relative to the camera pose.
            Pose cameraRelativePose = camera.getPose().inverse().compose(hit.getHitPose());
            float[] translation = cameraRelativePose.getTranslation();
            float[] quaternion = cameraRelativePose.getRotationQuaternion();
            ByteBuffer payload = ByteBuffer.allocate(4 * (translation.length + quaternion.length));
            FloatBuffer floatBuffer = payload.asFloatBuffer();
            floatBuffer.put(translation);
            floatBuffer.put(quaternion);

            try {
              frame.recordTrackData(ANCHOR_TRACK_ID, payload);
            } catch (IllegalStateException e) {
              Log.e(TAG, "Error in recording anchor into external data track.", e);
            }
          }
          // ... omitted code ...
  }

A razão pela qual persistimos o Pose relativo da câmera em vez do Pose relativo global é porque a origem mundial de uma sessão de gravação e a origem mundial de uma sessão de reprodução não são as mesmas. A origem mundial de uma sessão de gravação começa na primeira vez que a sessão é retomada, quando Session.resume() é chamado pela primeira vez. A origem mundial de uma sessão de reprodução começa quando o primeiro frame é gravado, quando Session.resume() é chamado pela primeira vez após Session.startRecording().

Criação da âncora de reprodução

A recriação de um Anchor é simples. Adicione uma função chamada createRecordedAnchors() em HelloArActivity.java.

// Add imports to the beginning of the file.
import com.google.ar.core.TrackData;

  // Extract poses from the ANCHOR_TRACK_ID track, and create new anchors.
  private void createRecordedAnchors(Frame frame, Camera camera) {
    // Get all `ANCHOR_TRACK_ID` TrackData from the frame.
    for (TrackData trackData : frame.getUpdatedTrackData(ANCHOR_TRACK_ID)) {
      ByteBuffer payload = trackData.getData();
      FloatBuffer floatBuffer = payload.asFloatBuffer();

      // Extract translation and quaternion from TrackData payload.
      float[] translation = new float[3];
      float[] quaternion = new float[4];

      floatBuffer.get(translation);
      floatBuffer.get(quaternion);

      // Transform the recorded anchor pose
      // from the camera coordinate
      // into world coordinates.
      Pose worldPose = camera.getPose().compose(new Pose(translation, quaternion));

      // Re-create an anchor at the recorded pose.
      Anchor recordedAnchor = session.createAnchor(worldPose);

      // Add the new anchor into the list of anchors so that
      // the AR marker can be displayed on top.
      anchors.add(recordedAnchor);
    }
  }

Chame createRecordedAnchors() na função onDrawFrame() em HelloArActivity.java.

  public void onDrawFrame(SampleRender render) {
    // ... omitted code ...

    // Insert after this line:
    // handleTap(frame, camera);

    // If the app is currently playing back a session, create recorded anchors.
    if (appState == AppState.Playingback) {
      createRecordedAnchors(frame, camera);
    }
    // ... omitted code ...
  }

Testar no dispositivo de destino

Conecte seu dispositivo móvel à máquina de desenvolvimento e clique em Executar no Android Studio.

Primeiro, toque no botão Gravar para gravar uma sessão. Durante a gravação, toque nos planos detectados para inserir alguns marcadores de RA.

Depois que a gravação for interrompida, toque no botão Reproduzir e selecione o arquivo que você acabou de gravar. A reprodução será iniciada. Observe como suas posições anteriores de marcadores de RA aparecem como você estava tocando no app.

Essa é toda a programação que você precisará fazer neste codelab.

6. Parabéns

Parabéns, você chegou ao fim deste codelab. Vamos rever o que você fez neste codelab:

  • Criou e executou da amostra Hello AR Java do ARCore
  • Adicionou um botão de gravação ao app para salvar uma sessão de RA em um arquivo MP4
  • Adicionou um botão de reprodução ao app para reproduzir uma sessão de RA de um arquivo MP4
  • Adicionou um novo recurso para salvar âncoras criadas pelo usuário no MP4 para reprodução

Você se divertiu neste codelab?

Sim Não

Você aprendeu algo útil neste codelab?

Sim Não

Você concluiu a criação do app neste codelab?

Sim Não