Introduzione all'API ARCore Recording and Riproduzione

1. Introduzione

La possibilità di salvare un'esperienza AR su un file MP4 e riprodurla dal file MP4 può essere utile sia per gli sviluppatori di app sia per gli utenti finali.

Esegui il debug e testa le nuove funzionalità dalla tua scrivania

L'utilizzo più semplice del record ARCore L'API Riproduzione è destinata agli sviluppatori. Sono finiti i tempi in cui dovevi creare ed eseguire l'app su un dispositivo di test, scollegare il cavo USB e camminare per testare una piccola modifica al codice. Ora ti basta registrare un MP4 nell'ambiente di test con il movimento previsto del telefono ed eseguire il test direttamente dalla scrivania.

Registrazione e riproduzione da diversi dispositivi

Con le API Registrazione e riproduzione, un utente può registrare una sessione su un dispositivo e un altro può riprodurre la stessa sessione su un altro. È possibile condividere un'esperienza AR con un altro utente. Ci sono moltissime possibilità.

È la prima volta che crei un'app ARCore?

No. Sì.

Come utilizzerai questo codelab?

Da leggere solo Leggilo e completa gli esercizi

Cosa creerai

In questo codelab, utilizzerai la funzionalità Registrazione e API Riproduzione per creare un'app che registri un'esperienza AR in un file MP4 e riproduca l'esperienza dallo stesso file. Scoprirai:

  • Come utilizzare l'API Recording per salvare una sessione AR in un file MP4.
  • Come utilizzare l'API Riproduzione per riprodurre una sessione AR da un file MP4.
  • Come registrare una sessione AR su un dispositivo e ripeterla su un altro.

Che cosa ti serve

In questo codelab modificherai l'app Hello AR Java, creata con l'SDK ARCore Android. Avrai bisogno di hardware e software specifici per continuare.

Requisiti hardware

Requisiti software

Per ottenere risultati ottimali, dovresti inoltre avere una conoscenza di base di ARCore.

2. Configurazione dell'ambiente di sviluppo

Per prima cosa, configura l'ambiente di sviluppo.

Scarica l'SDK ARCore per Android

Fai clic su per scaricare l'SDK.

Decomprimi l'SDK ARCore Android

Dopo aver scaricato l'SDK Android sul tuo computer, decomprimi il file e vai alla directory arcore-android-sdk-1.24/samples/hello_ar_java. Questa è la directory principale dell'app con cui lavorerai.

hello-ar-java-extracted

Carica Hello AR Java in Android Studio

Avvia Android Studio e fai clic su Apri un progetto Android Studio esistente.

android-studio-open-projects

Nella finestra di dialogo visualizzata, seleziona arcore-android-sdk-1.24/samples/hello_ar_java e fai clic su Apri.

Attendi che Android Studio completi la sincronizzazione del progetto. Se manca un componente, l'importazione del progetto potrebbe non riuscire e verranno visualizzati messaggi di errore. Risolvi questi problemi prima di continuare.

Esegui l'app di esempio

  1. Connetti un dispositivo supportato da ARCore al tuo computer di sviluppo.
  2. Se il dispositivo viene riconosciuto correttamente, il nome del dispositivo dovrebbe apparire in Android Studio. android-studio-pixel-5.png
  3. Fai clic sul pulsante Esegui o seleziona Esegui > Esegui "app" per fare in modo che Android Studio installi e avvii l'app sul dispositivo. android-studio-run-button.png
  4. Verrà visualizzato un messaggio che ti chiede l'autorizzazione per scattare foto e registrare video. Seleziona Mentre usi questa app per concedere all'app le autorizzazioni di accesso alla fotocamera. Quindi, sullo schermo del dispositivo vedrai il tuo ambiente reale. hello-ar-java-permission
  5. Sposta il dispositivo in orizzontale per cercare aerei.
  6. Quando l'app rileva un aereo, viene visualizzata una griglia bianca. Toccala per posizionare un indicatore su quell'aereo. Introduzione al posizionamento dell'AR

Operazioni eseguite in questo passaggio

  • Configura il progetto Java Hello AR
  • Crea ed esegui l'app di esempio su un dispositivo supportato da ARCore

Successivamente, registrerai una sessione AR su un file MP4.

3. Registrare una sessione ARCore su un file MP4

In questo passaggio aggiungeremo la funzionalità di registrazione. È composto da:

  • Un pulsante per avviare o interrompere la registrazione.
  • Funzioni di archiviazione per salvare il file MP4 sul dispositivo.
  • Chiamate per avviare o interrompere la registrazione della sessione ARCore.

Pulsante Aggiungi UI per record

Prima di implementare la registrazione, aggiungi un pulsante nella UI in modo che l'utente possa comunicare ad ARCore quando avviare o interrompere la registrazione.

Nel riquadro Progetto, apri il file app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

Per impostazione predefinita, Android Studio utilizzerà la visualizzazione Struttura dopo l'apertura del file app/res/layout/activity_main.xml. Fai clic sul pulsante Codice nell'angolo in alto a destra della scheda per passare alla visualizzazione codice.

swith-to-the-code-view.png

In activity_main.xml, aggiungi il seguente codice prima del tag di chiusura per creare il nuovo pulsante Record e impostare il relativo gestore di eventi su un metodo chiamato 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" />

Dopo aver aggiunto il codice riportato sopra, potrebbe essere visualizzato temporaneamente un errore: Corresponding method handler 'public void onClickRecord(android.view.View)' not found". È un comportamento previsto. Risolvi l'errore creando la funzione onClickRecord() nei prossimi passaggi.

Modifica il testo del pulsante in base allo stato

Il pulsante Registra gestisce sia la registrazione sia l'interruzione. Quando l'app non sta registrando dati, dovrebbe visualizzare la parola "Registra". Quando l'app registra dati, il pulsante dovrebbe cambiare per visualizzare la parola "Interrompi".

Per assegnare questa funzionalità al pulsante, l'app deve conoscere il suo stato attuale. Il seguente codice crea una nuova enum denominata AppState per rappresentare lo stato di funzionamento dell'app e tiene traccia di specifiche modifiche di stato tramite una variabile membro privata denominata appState. Aggiungilo a HelloArActivity.java, all'inizio del corso HelloArActivity.

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

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

Ora che puoi monitorare lo stato interno dell'app, crea una funzione denominata updateRecordButton() che modifichi il testo del pulsante in base allo stato attuale dell'app. Aggiungi il seguente codice all'interno del corso HelloArActivity in 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;
    }
  }

A questo punto, crea il metodo onClickRecord() che controlla lo stato dell'app, lo modifica a quello successivo e chiama updateRecordButton() per modificare l'UI del pulsante. Aggiungi il seguente codice all'interno del corso HelloArActivity in 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();
  }

Abilita l'app per avviare la registrazione

Devi fare solo due cose per iniziare a registrare in ARCore:

  1. Specifica l'URI del file di registrazione in un oggetto RecordingConfig.
  2. Chiama session.startRecording con l'oggetto RecordingConfig

Il resto è solo il codice boilerplate: configurazione, logging e controllo della correttezza.

Crea una nuova funzione denominata startRecording() che registra i dati e li salva in un URI MP4. Aggiungi il seguente codice all'interno del corso HelloArActivity in 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;
  }

Per mettere in pausa e riprendere in modo sicuro una sessione ARCore, crea pauseARCoreSession() e resumeARCoreSession() in 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;
  }

Abilita l'app per interrompere la registrazione

Crea una funzione denominata stopRecording() in HelloArActivity.java per interrompere la registrazione di nuovi dati da parte dell'app. Questa funzione chiama session.stopRecording() e invia un errore al log della console se l'app non riesce a interrompere la registrazione.

  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;
  }

Progettare l'archiviazione di file utilizzando l'archiviazione con ambito Android 11

Le funzioni relative all'archiviazione in questo codelab sono progettate secondo i nuovi requisiti di archiviazione con ambito di Android 11.

Apporta alcune piccole modifiche al file app/build.gradle per avere come target Android 11. Nel riquadro Android Studio Project, questo file si trova nel nodo Gradle Script, associato al modulo app.

app-build.gradle.png

Cambia compileSdkVersion e targetSdkVersion in 30.

    compileSdkVersion 30
    defaultConfig {
      targetSdkVersion 30
    }

Per la registrazione, usa l'API Android MediaStore per creare il file MP4 nella directory Film condiviso.

Crea una funzione denominata createMp4File() in 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;
  }

Gestire le autorizzazioni per l'archiviazione

Se utilizzi un dispositivo Android 11, puoi iniziare a testare il codice. Per supportare i dispositivi con Android 10 o versioni precedenti, devi concedere le autorizzazioni di accesso allo spazio di archiviazione dell'app per salvare i dati nel file system del dispositivo di destinazione.

In AndroidManifest.xml, dichiara che l'app ha bisogno di autorizzazioni di lettura e scrittura per lo spazio di archiviazione prima di Android 11 (livello API 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" />

Aggiungi una funzione helper denominata checkAndRequestStoragePermission() in HelloArActivity.java per richiedere le autorizzazioni WRITE_EXTERNAL_STORAGE durante il runtime.

// 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 utilizzi il livello API 29 o precedente, aggiungi un controllo delle autorizzazioni per lo spazio di archiviazione nella parte superiore di createMp4File() ed esci anticipatamente dalla funzione se l'app non dispone delle autorizzazioni corrette. Il livello API 30 (Android 11) non richiede l'autorizzazione per lo spazio di archiviazione per accedere ai file in 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 ...
  }

Registra dal dispositivo di destinazione

È il momento di vedere cosa hai costruito finora. Collega il dispositivo mobile al computer di sviluppo e fai clic su Esegui in Android Studio.

Dovresti vedere un pulsante rosso Registra in basso a sinistra sullo schermo. Toccandola, il testo dovrebbe cambiare in Interrompi. Muovi il dispositivo per registrare una sessione e fai clic sul pulsante Interrompi quando desideri completare la registrazione. Verrà salvato un nuovo file chiamato arcore-xxxxxx_xxxxxx.mp4 nella memoria esterna del dispositivo.

record-button.png

Ora dovresti avere un nuovo file arcore-xxxxxx_xxxxxx.mp4 nella memoria esterna del dispositivo. Sui dispositivi Pixel 5, il percorso è /storage/emulated/0/Movies/. Il percorso è disponibile nella finestra di Logcat dopo l'avvio di una registrazione.

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

Visualizza la registrazione

Puoi utilizzare un'app di file system come Files by Google per visualizzare la registrazione o copiarla sul tuo computer di sviluppo. Di seguito sono riportati i due comandi adb per elencare e recuperare file dal dispositivo Android:

  • adb shell ls '$EXTERNAL_STORAGE/Movies/*' per mostrare i file nella directory Film nella memoria esterna del dispositivo
  • adb pull /absolute_path_from_previous_adb_shell_ls/arcore-xxxxxxxx_xxxxxx.mp4 per copiare il file dal dispositivo al computer di sviluppo

Questo è un output di esempio dopo aver utilizzato questi due comandi (da 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

Operazioni eseguite in questo passaggio

  • Aggiunto un pulsante per avviare e interrompere la registrazione
  • Funzioni implementate per avviare e interrompere la registrazione
  • Hai testato l'app sul dispositivo
  • L'MP4 registrato è stato copiato sul computer e verificato

Dopodiché, riprodurrai una sessione AR da un file MP4.

4. Riprodurre una sessione ARCore da un file MP4

Ora sono disponibili un pulsante Registra e alcuni file MP4 contenenti le sessioni registrate. Ora puoi riprodurli utilizzando l'API ARCore Playback.

Aggiungi UI per pulsante di riproduzione

Prima di implementare la riproduzione, aggiungi un pulsante nella UI in modo che l'utente possa comunicare ad ARCore quando deve iniziare e interrompere la riproduzione della sessione.

Nel riquadro Progetto, apri il file app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

In activity_main.xml, aggiungi il codice seguente prima del tag di chiusura per creare il nuovo pulsante Riproduzione e impostare il relativo gestore di eventi su un metodo chiamato onClickPlayback(). Questo pulsante è simile al pulsante Registra e viene visualizzato sul lato destro dello schermo.

  <!--
    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" />

Aggiorna i pulsanti durante la riproduzione

Ora l'app ha un nuovo stato denominato Playingback. Aggiorna l'enumerazione AppState e tutte le funzioni esistenti che utilizzano appState come argomento per gestire questa operazione.

Aggiungi Playingback all'enum AppState in HelloArActivity.java:

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

Se il pulsante Registra è ancora presente sullo schermo durante la riproduzione, l'utente potrebbe selezionarlo per errore. Per evitare che ciò accada, nascondi il pulsante Registra durante la riproduzione. In questo modo non dovrai gestire lo stato Playingback in onClickRecord().

Modifica la funzione updateRecordButton() in HelloArActivity.java per nascondere il pulsante Registra quando l'app è nello stato 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;
    }
  }

Analogamente, nascondi il pulsante Riproduzione quando l'utente sta registrando una sessione e impostalo su "Interrompi". quando l'utente sta riproducendo attivamente una sessione. In questo modo, possono interrompere la riproduzione senza dover attendere il completamento automatico.

Aggiungi una funzione updatePlaybackButton() in 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;
    }
  }

Infine, aggiorna onClickRecord() per chiamare il numero updatePlaybackButton(). Aggiungi la seguente riga a HelloArActivity.java:

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

Selezionare un file con il pulsante di riproduzione

Se toccato, il pulsante Riproduzione deve consentire all'utente di selezionare un file da riprodurre. Su Android, la selezione dei file viene gestita nel selettore file di sistema in un'altra attività. Ciò avviene mediante Storage Access Framework (SAF). Quando l'utente seleziona un file, l'app viene richiamato con il nome onActivityResult(). Inizierai la riproduzione effettiva all'interno di questa funzione di callback.

In HelloArActivity.java, crea una funzione onClickPlayback() per selezionare il file e interrompere la riproduzione.

  // 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();
  }

In HelloArActivity.java, crea una funzione selectFileToPlayback() che selezioni un file dal dispositivo. Per selezionare un file dal file system Android, utilizza un 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 è una costante per identificare questa richiesta. Puoi definirlo utilizzando qualsiasi valore segnaposto all'interno di HelloArActivity in HelloArActivity.java:

  private int REQUEST_MP4_SELECTOR = 1;

Esegui l'override della funzione onActivityResult() in HelloArActivity.java per gestire il callback dal selettore file.

  // 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);
  }

Attiva l'app per avviare la riproduzione

Una sessione ARCore richiede tre chiamate API per riprodurre un file MP4:

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

In HelloArActivity.java, crea la funzione 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;
  }

Attiva l'app per interrompere la riproduzione

Crea una funzione denominata stopPlayingback() in HelloArActivity.java per gestire le modifiche dello stato dell'app dopo:

  1. La riproduzione di MP4 è stata interrotta dall'utente
  2. La riproduzione MP4 è stata completata automaticamente

Se l'utente ha interrotto la riproduzione, l'app dovrebbe tornare allo stato in cui si trovava al momento del primo avvio.

  // 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;
  }

La riproduzione può interrompersi naturalmente anche dopo che il player ha raggiunto la fine del file MP4. In questo caso, stopPlayingback() dovrebbe riportare lo stato dell'app a Idle. In onDrawFrame(), controlla PlaybackStatus. Se è FINISHED, chiama la funzione stopPlayingback() nel thread dell'interfaccia utente.

  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 ...
  }

Riprodurre dal dispositivo di destinazione

È il momento di vedere cosa hai costruito finora. Collega il dispositivo mobile al computer di sviluppo e fai clic su Esegui in Android Studio.

All'avvio dell'app, dovresti vedere una schermata con un pulsante rosso Registra a sinistra e un pulsante Riproduzione verde a destra.

playback-button.png

Tocca il pulsante Riproduzione e seleziona uno dei file MP4 appena registrato. Se non vedi nomi di file che iniziano con arcore-, è possibile che sul dispositivo non sia visualizzata la cartella Filmati. In questo caso, vai a Modello telefono > Filmati utilizzando il menu nell'angolo in alto a sinistra. Potresti anche dover attivare l'opzione Mostra memoria interna per visualizzare la cartella del modello del telefono.

show-internal-storage-button.png

nativate-to-movies-file-picker.jpg

Tocca un nome file sullo schermo per selezionare il file MP4. L'app dovrebbe riprodurre il file MP4.

playback-stop-button.png

Una differenza tra la riproduzione di una sessione e la riproduzione di un video normale è che puoi interagire con la sessione registrata. Tocca un aereo rilevato per posizionare indicatori sullo schermo.

posizionamento-riproduzione

Operazioni eseguite in questo passaggio

  • Aggiunta di un pulsante per avviare e interrompere la riproduzione
  • Implementata una funzione per avviare e interrompere la registrazione dell'app
  • È stata riprodotta una sessione ARCore registrata in precedenza sul dispositivo.

5. Registrare dati aggiuntivi in MP4

Con ARCore 1.24, è possibile registrare informazioni aggiuntive nel file MP4. Puoi registrare Pose dei posizionamenti degli oggetti AR e poi, durante la riproduzione, creare gli oggetti AR nella stessa posizione.

Configura la nuova traccia da registrare

Definisci una nuova traccia con un UUID e un tag MIME in 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 ...
  }

Aggiorna il codice esistente per creare l'oggetto RecordingConfig con una chiamata a 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 ...
  }

Salva la posa dell'ancoraggio durante la registrazione

Ogni volta che l'utente tocca un aereo rilevato, viene posizionato un indicatore AR su Anchor, la cui posa verrà aggiornata da ARCore.

Registra la posa di Anchor nel frame in cui è stato creato, se stai ancora registrando la sessione ARCore.

Modifica la funzione handleTap() in 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 ...
  }

Il motivo per cui memorizziamo il relativo della videocamera Pose anziché il relativo mondiale Pose è perché l'origine mondiale di una sessione di registrazione e l'origine mondiale di una sessione di riproduzione sono diverse. L'origine mondiale di una sessione di registrazione inizia la prima volta che la sessione viene ripresa, quando viene chiamato per la prima volta Session.resume(). L'origine mondiale di una sessione di riproduzione inizia quando viene registrato il primo frame, quando il Session.resume() viene chiamato per la prima volta dopo Session.startRecording().

Creazione dell'ancoraggio di riproduzione

Ricreare una Anchor è semplice. Aggiungi una funzione denominata createRecordedAnchors() in 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);
    }
  }

Richiama createRecordedAnchors() nella funzione onDrawFrame() in 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 ...
  }

Esegui il test sul dispositivo di destinazione

Collega il dispositivo mobile al computer di sviluppo e fai clic su Esegui in Android Studio.

Innanzitutto, tocca il pulsante Registra per registrare una sessione. Durante la registrazione, tocca gli aerei rilevati per posizionare alcuni indicatori AR.

Al termine della registrazione, tocca il pulsante Riproduzione e seleziona il file appena registrato. La riproduzione dovrebbe iniziare. Nota come i precedenti posizionamenti degli indicatori AR vengono visualizzati proprio mentre toccavi l'app.

Questo è tutto ciò che dovrai fare per questo codelab.

6. Complimenti

Complimenti, hai raggiunto la fine di questo codelab. Torniamo a cosa hai fatto in questo codelab:

  • Crea ed esegui l'esempio Hello AR Java di ARCore.
  • Aggiunto un pulsante Registra all'app per salvare una sessione AR in un file MP4
  • Aggiunta di un pulsante di riproduzione all'app per riprodurre una sessione AR da un file MP4
  • Aggiunta una nuova funzionalità per salvare gli ancoraggi creati dall'utente nell'MP4 per riprodurli.

Ti sei divertito con questo codelab?

No

Hai imparato qualcosa di utile in questo codelab?

No

Hai completato la creazione dell'app in questo codelab?

No