Wprowadzenie do ARCore Notification API

1. Wprowadzenie

Możliwość zapisania środowiska AR w pliku MP4 i odtwarzania z niego jest przydatne zarówno dla deweloperów aplikacji, jak i użytkowników.

Debuguj i testuj nowe funkcje bez wychodzenia z biura

Najprostsze zastosowanie ARCore Record Interfejs Odtwarzanie API jest przeznaczony dla programistów. Minęły już czasy, kiedy trzeba było skompilować i uruchomić aplikację na urządzeniu testowym, odłączyć kabel USB i obejść się, aby przetestować drobną zmianę kodu. Teraz wystarczy, że nagrasz plik MP4 w środowisku testowym z oczekiwanym ruchem telefonu i przetestujesz go bezpośrednio z biurka.

Nagrywanie i odtwarzanie na różnych urządzeniach

Dzięki interfejsom API do nagrywania i odtwarzania jeden użytkownik może nagrać sesję na jednym urządzeniu, a drugi ją odtworzyć na innym. Istnieje możliwość udostępnienia funkcji AR innemu użytkownikowi. Jest wiele możliwości.

Czy tworzysz aplikację ARCore po raz pierwszy?

Nie. Tak.

Jak będziesz używać tego ćwiczenia z programowania?

tylko do przeczytania. Przeczytaj go i wykonaj ćwiczenia

Co utworzysz

W ramach tego ćwiczenia w Codelabs wykorzystasz funkcję API do tworzenia aplikacji, które nagrywają wrażenia AR w pliku MP4 i odtwarzają je z tego samego pliku. Dowiesz się:

  • Jak zapisać sesję AR w pliku MP4 za pomocą interfejsu Reporting API.
  • Jak użyć interfejsu Playback API do ponownego odtwarzania sesji AR z pliku MP4.
  • Jak nagrać sesję AR na jednym urządzeniu i ponownie ją odtworzyć na innym.

Czego potrzebujesz

W tym ćwiczeniu w Codelabs zmodyfikujesz aplikację Hello AR Java, która została stworzona za pomocą pakietu SDK ARCore Android. Będzie Ci potrzebny odpowiedni sprzęt i oprogramowanie.

Wymagania sprzętowe

Wymagania dotyczące oprogramowania

Aby uzyskać najlepsze wyniki, musisz też mieć podstawową wiedzę na temat ARCore.

2. Konfigurowanie środowiska programistycznego

Zacznij od skonfigurowania środowiska programistycznego.

Pobierz pakiet ARCore Android SDK

Kliknij , aby pobrać pakiet SDK.

Rozpakuj pakiet ARCore Android SDK

Gdy pobierzesz pakiet Android SDK na swój komputer, rozpakuj go i przejdź do katalogu arcore-android-sdk-1.24/samples/hello_ar_java. Jest to katalog główny aplikacji, z którą będziesz pracować.

hello-ar-java-extracted

Ładowanie środowiska Java Hello AR do Android Studio

Uruchom Android Studio i kliknij Otwórz istniejący projekt Android Studio.

android-studio-open-projects

W wyświetlonym oknie dialogowym wybierz arcore-android-sdk-1.24/samples/hello_ar_java i kliknij Otwórz.

Poczekaj, aż Android Studio zakończy synchronizację projektu. Jeśli brakuje komponentu, zaimportowanie projektu może się nie udać i wyświetlą się komunikaty o błędach. Zanim przejdziesz dalej, rozwiąż te problemy.

Uruchamianie przykładowej aplikacji

  1. Połącz urządzenie obsługujące ARCore z maszyną, której używasz do programowania.
  2. Jeśli urządzenie zostanie prawidłowo rozpoznane, w Android Studio powinna pojawić się jego nazwa. android-studio-pixel-5.png
  3. Kliknij przycisk Uruchom lub wybierz Uruchom > Uruchom „aplikacja”, aby Android Studio mógł zainstalować i uruchomić aplikację na urządzeniu. android-studio-run-button.png
  4. Pojawi się prośba o pozwolenie na robienie zdjęć i nagrywanie filmów. Wybierz Podczas używania tej aplikacji, aby przyznać aplikacji uprawnienia do aparatu. Dzięki temu na ekranie urządzenia zobaczysz swoje rzeczywiste środowisko. hello-ar-java-permission
  5. Aby wyszukać samoloty, przesuń urządzenie poziomo.
  6. Gdy aplikacja wykryje samolot, pojawi się biała siatka. Kliknij go, by umieścić znacznik na tym samolocie. Miejsce docelowe Hello AR

Wykonane na tym etapie czynności

  • Konfigurowanie projektu Java Hello AR
  • Utworzysz i uruchomisz przykładową aplikację na urządzeniu obsługującym ARCore.

Następnie nagrasz sesję AR w pliku MP4.

3. Nagrywanie sesji ARCore do pliku MP4

Na tym etapie dodamy funkcję nagrywania. Składa się z tych elementów:

  • Przycisk rozpoczęcia lub zatrzymania nagrywania.
  • Funkcje pamięci masowej umożliwiające zapisanie pliku MP4 na urządzeniu.
  • Połączenia rozpoczynające lub zatrzymujące nagrywanie sesji ARCore.

Dodaj interfejs użytkownika do przycisku nagrywania

Zanim wdrożysz nagrywanie, dodaj w interfejsie przycisk, aby użytkownik mógł informować ARCore o rozpoczęciu lub zakończeniu nagrywania.

W panelu Projekt otwórz plik app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

Po otwarciu pliku app/res/layout/activity_main.xml Android Studio domyślnie używa widoku projektu. Kliknij przycisk Kod w prawym górnym rogu karty, by przełączyć się na widok kodu.

swith-to-the-code-view.png

W activity_main.xml dodaj ten kod przed tagiem zamykającym, aby utworzyć nowy przycisk Nagraj i ustawić jego moduł obsługi zdarzeń na metodę o nazwie 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" />

Po dodaniu tego kodu może przez jakiś czas wyświetlać się błąd: Corresponding method handler 'public void onClickRecord(android.view.View)' not found". To normalne. W kilku kolejnych krokach utworzysz funkcję onClickRecord().

Zmiana tekstu przycisku na podstawie stanu

Przycisk Nagraj obsługuje zarówno nagrywanie, jak i zatrzymywanie. Gdy aplikacja nie rejestruje danych, powinno wyświetlać się słowo „Record”. Gdy aplikacja rejestruje dane, na przycisku powinien pojawić się komunikat „Zatrzymaj”.

Aby przyznać przyciskowi tę funkcję, aplikacja musi znać bieżący stan. Ten kod tworzy nowe wyliczenie o nazwie AppState reprezentujące stan aplikacji i śledzi określone zmiany stanu za pomocą prywatnej zmiennej użytkownika o nazwie appState. Dodaj je do zadania HelloArActivity.java na początku zajęć HelloArActivity.

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

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

Możesz śledzić wewnętrzny stan aplikacji, więc utwórz funkcję o nazwie updateRecordButton(), która zmienia tekst przycisku odpowiednio do bieżącego stanu aplikacji. Dodaj poniższy kod do klasy HelloArActivity w 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;
    }
  }

Następnie utwórz metodę onClickRecord(), która sprawdza stan aplikacji, zmienia ją na następną i wywołuje metodę updateRecordButton(), aby zmienić interfejs przycisku. Dodaj poniższy kod do klasy HelloArActivity w 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();
  }

Aby rozpocząć nagrywanie, włącz aplikację

Aby zacząć nagrywać w ARCore, musisz wykonać tylko 2 czynności:

  1. Podaj identyfikator URI pliku nagrania w obiekcie RecordingConfig.
  2. Wywołaj funkcję session.startRecording za pomocą obiektu RecordingConfig

Pozostała część to tylko stały kod: konfiguracja, rejestrowanie i sprawdzanie poprawności.

Utwórz nową funkcję o nazwie startRecording(), która rejestruje dane i zapisuje je w identyfikatorze URI MP4. Dodaj poniższy kod do klasy HelloArActivity w 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;
  }

Aby bezpiecznie wstrzymać i wznowić sesję ARCore, utwórz pauseARCoreSession() i resumeARCoreSession() w 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;
  }

Aby zatrzymać rejestrowanie, włącz aplikację

Utwórz funkcję o nazwie stopRecording() w elemencie HelloArActivity.java, aby zatrzymać rejestrowanie nowych danych przez aplikację. Ta funkcja wywołuje session.stopRecording() i wysyła błąd do logu konsoli, jeśli aplikacja nie może zatrzymać nagrywania.

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

Projektowanie miejsca na pliki z wykorzystaniem pamięci w zakresie Androida 11

Funkcje związane z pamięcią opisane w tym ćwiczeniu w programie zostały opracowane zgodnie z nowymi wymaganiami dotyczącymi miejsca na dane w Androidzie 11.

Wprowadź niewielkie zmiany w pliku app/build.gradle, aby kierować aplikację na Androida 11. W panelu projektu Android Studio plik ten znajduje się w węźle Gradle Scripts powiązanym z modułem aplikacja.

app-build.gradle.png

Zmień compileSdkVersion i targetSdkVersion na 30.

    compileSdkVersion 30
    defaultConfig {
      targetSdkVersion 30
    }

W przypadku nagrywania utwórz plik MP4 w udostępnionym katalogu Movie, korzystając z interfejsu API Android MediaStore.

Utwórz funkcję o nazwie createMp4File() w 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;
  }

Obsługa uprawnień do przechowywania danych

Jeśli używasz urządzenia z Androidem 11, możesz rozpocząć testowanie kodu. Aby zapewnić obsługę urządzeń z Androidem 10 lub starszym, musisz przyznać aplikacji uprawnienia do zapisywania danych w systemie plików urządzenia docelowego.

W języku AndroidManifest.xml zadeklaruj, że aplikacja wymaga uprawnień do odczytu i zapisu w pamięci przed Androidem 11 (poziom interfejsu 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" />

Dodaj w narzędziu HelloArActivity.java funkcję pomocniczą o nazwie checkAndRequestStoragePermission(), aby prosić o uprawnienia WRITE_EXTERNAL_STORAGE w czasie działania.

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

Jeśli korzystasz z interfejsu API na poziomie 29 lub wyższym, sprawdź, czy aplikacja nie ma odpowiednich uprawnień, i sprawdź uprawnienia dostępu do pamięci na górze createMp4File() i wyjdź z tej funkcji wcześniej. Interfejs API poziomu 30 (Android 11) nie wymaga uprawnień do pamięci, by uzyskać dostęp do plików w 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 ...
  }

Nagrywaj z urządzenia docelowego

Czas zobaczyć, co udało Ci się już osiągnąć. Podłącz urządzenie mobilne do komputera i kliknij Uruchom w Android Studio.

W lewym dolnym rogu ekranu powinien pojawić się czerwony przycisk Nagraj. Kliknięcie go powinno zmienić tekst na Zatrzymaj. Poruszaj urządzeniem wokół siebie, aby nagrać sesję, a gdy chcesz zakończyć nagrywanie, kliknij przycisk Zatrzymaj. Powinno to zapisać nowy plik o nazwie arcore-xxxxxx_xxxxxx.mp4 w pamięci zewnętrznej urządzenia.

record-button.png

W pamięci zewnętrznej powinien być teraz nowy plik arcore-xxxxxx_xxxxxx.mp4. Na urządzeniach Pixel 5 ścieżka to /storage/emulated/0/Movies/. Ścieżkę znajdziesz w oknie Logcat po rozpoczęciu nagrywania.

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

Wyświetl nagranie

Nagranie możesz wyświetlić lub skopiować do komputera programisty, przy użyciu aplikacji systemu plików, takiej jak Files by Google. Poniżej znajdziesz 2 polecenia adb, które pozwalają wyświetlić listę plików i pobrać je z urządzenia z Androidem:

  • adb shell ls '$EXTERNAL_STORAGE/Movies/*', aby wyświetlić pliki z katalogu Filmy w pamięci zewnętrznej urządzenia.
  • adb pull /absolute_path_from_previous_adb_shell_ls/arcore-xxxxxxxx_xxxxxx.mp4, aby skopiować plik z urządzenia na komputer, którego używasz do programowania.

Oto przykładowe dane wyjściowe po użyciu tych 2 poleceń (w systemie 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

Wykonane na tym etapie czynności

  • Dodano przycisk rozpoczynania i zatrzymywania nagrywania
  • Wdrożono funkcje rozpoczynania i zatrzymywania nagrywania
  • Aplikacja została przetestowana na urządzeniu
  • Nagrany plik MP4 został skopiowany na komputer i zweryfikowany

Następnie odtwórz sesję AR z pliku MP4.

4. Odtwarzanie sesji ARCore z pliku MP4

Masz teraz przycisk Nagraj i niektóre pliki MP4 z nagranymi sesjami. Teraz możesz je odtworzyć za pomocą interfejsu ARCore Playback API.

Dodanie interfejsu do przycisku odtwarzania

Zanim zaimplementujesz odtwarzanie, dodaj w interfejsie przycisk, aby użytkownik mógł informować ARCore o tym, kiedy ma się rozpocząć i zakończyć odtwarzanie sesji.

W panelu Projekt otwórz plik app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

W activity_main.xml dodaj poniższy kod przed tagiem zamykającym, aby utworzyć nowy przycisk Odtwarzanie i ustawić jego moduł obsługi zdarzeń na metodę onClickPlayback(). Ten przycisk przypomina przycisk Nagraj i jest wyświetlany po prawej stronie ekranu.

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

Aktualizuj przyciski podczas odtwarzania

Aplikacja ma teraz nowy stan: Playingback. Zaktualizuj wyliczenie AppState i wszystkie istniejące funkcje, które jako argumenty obsługują appState.

Dodaj Playingback do wyliczenia AppState w HelloArActivity.java:

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

Jeżeli podczas odtwarzania przycisk Nagraj jest nadal widoczny na ekranie, użytkownik może kliknąć go przez przypadek. Aby tego uniknąć, ukryj przycisk Nagraj podczas odtwarzania. Dzięki temu nie będzie trzeba obsługiwać stanu Playingback w onClickRecord().

Zmień funkcję updateRecordButton() w HelloArActivity.java, aby ukryć przycisk Nagraj, gdy aplikacja jest w stanie 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;
    }
  }

Podobnie ukrywaj przycisk Odtwarzanie, gdy użytkownik nagrywa sesję, i zmień go na „Zatrzymaj”. gdy użytkownik aktywnie odtwarza sesję. Dzięki temu mogą zatrzymać odtwarzanie bez konieczności czekania, aż samo się zakończy.

Dodaj funkcję updatePlaybackButton() w 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;
    }
  }

Zaktualizuj urządzenie onClickRecord(), aby dzwoniło pod numer updatePlaybackButton(). Dodaj do pliku HelloArActivity.java ten wiersz:

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

Wybierz plik za pomocą przycisku odtwarzania

Po kliknięciu przycisk Odtwarzanie powinien umożliwiać użytkownikowi wybranie pliku do odtworzenia. Na Androidzie wybór plików jest obsługiwany w systemowym selektorze plików w innej aktywności. Można to zrobić za pomocą platformy Storage Access Framework (SAF). Gdy użytkownik wybierze plik, aplikacja otrzyma wywołanie zwrotne o nazwie onActivityResult(). Rozpoczniesz odtwarzanie w obrębie tej funkcji wywołania zwrotnego.

W HelloArActivity.java utwórz funkcję onClickPlayback(), by wybrać plik i zatrzymać odtwarzanie.

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

W programie HelloArActivity.java utwórz funkcję selectFileToPlayback(), która wybiera plik z urządzenia. Aby wybrać plik z Android File System, użyj intencji 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 jest stałą wartością żądania. Można ją zdefiniować za pomocą dowolnej wartości zmiennej wewnątrz HelloArActivity w HelloArActivity.java:

  private int REQUEST_MP4_SELECTOR = 1;

Zastąp funkcję onActivityResult() w zasadzie HelloArActivity.java, aby obsługiwać wywołanie zwrotne z selektora plików.

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

Aby rozpocząć odtwarzanie, włącz aplikację

Sesja ARCore wymaga 3 wywołań interfejsu API, aby odtworzyć plik MP4:

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

W HelloArActivity.java utwórz funkcję 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;
  }

Aby zatrzymać odtwarzanie, włącz aplikację

Utwórz w narzędziu HelloArActivity.java funkcję o nazwie stopPlayingback() do obsługi zmian stanu aplikacji po:

  1. Odtwarzanie w formacie MP4 zostało zatrzymane przez użytkownika
  2. Odtwarzanie w formacie MP4 zakończyło się samoczynnie

Jeśli użytkownik zatrzymał odtwarzanie, aplikacja powinna wrócić do stanu z chwili jej pierwszego uruchomienia.

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

Odtwarzanie może się również w naturalny sposób zostać zatrzymane, gdy odtwarzacz osiągnie koniec pliku MP4. W takim przypadku stopPlayingback() powinien zmienić stan aplikacji z powrotem na Idle. W onDrawFrame() sprawdź PlaybackStatus. Jeśli to FINISHED, wywołaj funkcję stopPlayingback() w wątku interfejsu.

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

Odtwarzanie z urządzenia docelowego

Czas zobaczyć, co udało Ci się już osiągnąć. Podłącz urządzenie mobilne do komputera i kliknij Uruchom w Android Studio.

Po uruchomieniu aplikacji powinien wyświetlić się ekran z czerwonym przyciskiem Nagraj po lewej stronie i zielonym przyciskiem Odtwórz po prawej.

playback-button.png

Dotknij przycisku Odtwarzanie i wybierz jeden z nagranych przed chwilą plików MP4. Jeśli nie widzisz żadnych plików, których nazwy zaczynają się od arcore-, być może na urządzeniu nie wyświetla się folder Filmy. Przejdź do sekcji Model telefonu > Filmy, korzystając z menu w lewym górnym rogu. Konieczne może być też włączenie opcji Pokaż pamięć wewnętrzną.

show-internal-storage-button.png

nativate-to-movies-file-picker.jpg

Kliknij nazwę pliku na ekranie, aby wybrać plik MP4. Aplikacja powinna odtworzyć plik MP4.

playback-stop-button.png

Różnica między odtwarzaniem sesji a odtwarzaniem zwykłego filmu polega na tym, że na nagraną sesję możesz wejść w interakcję. Kliknij wykryty samolot, by umieścić znaczniki na ekranie.

miejsce docelowe odtwarzania

Wykonane na tym etapie czynności

  • Dodano przycisk do rozpoczynania i zatrzymywania odtwarzania
  • Dodano funkcję uruchamiania i zatrzymywania nagrywania przez aplikację.
  • Na urządzeniu została odtworzona wcześniej nagrana sesja ARCore

5. Nagrywaj dodatkowe dane w formacie MP4.

Format ARCore 1.24 umożliwia rejestrowanie dodatkowych informacji w pliku MP4. Możesz zarejestrować Pose miejsc docelowych obiektów AR, a następnie podczas odtwarzania tworzyć obiekty AR w tym samym miejscu.

Skonfiguruj nową ścieżkę do rejestrowania

Zdefiniuj nową ścieżkę z identyfikatorem UUID i tagiem MIME w narzędziu 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 ...
  }

Zaktualizuj kod wyjścia, aby utworzyć obiekt RecordingConfig z wywołaniem 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 ...
  }

Zapisz pozycję kotwicy podczas nagrywania

Za każdym razem, gdy użytkownik kliknie wykryty samolot, na urządzeniu Anchor zostanie umieszczony znacznik AR, którego pozycja zostanie zaktualizowana przez ARCore.

Jeśli nadal rejestrujesz sesję ARCore, zarejestruj pozycję obiektu Anchor w kadrze, w którym został on utworzony.

Zmodyfikuj funkcję handleTap() w 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 ...
  }

Powodem zachowywania względnego Pose kamery zamiast względnego Pose jest to, że światowe pochodzenie sesji nagrywania i światowe pochodzenie sesji odtwarzania nie są takie same. Światowe pochodzenie sesji nagrywania rozpoczyna się przy pierwszym jej wznowieniu, czyli po wywołaniu funkcji Session.resume(). Światowe pochodzenie sesji odtwarzania rozpoczyna się po zarejestrowaniu pierwszej klatki, gdy funkcja Session.resume() jest po raz pierwszy wywoływana po Session.startRecording().

Tworzenie kotwicy odtwarzania

Odtworzenie elementu Anchor jest proste. Dodaj funkcję o nazwie createRecordedAnchors() w 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);
    }
  }

Wywołaj createRecordedAnchors() w funkcji onDrawFrame() w funkcji 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 ...
  }

Testowanie na urządzeniu docelowym

Podłącz urządzenie mobilne do komputera i kliknij Uruchom w Android Studio.

Najpierw kliknij przycisk Nagraj, aby nagrać sesję. Podczas nagrywania kliknij wykryte samoloty, by umieścić kilka znaczników AR.

Po zatrzymaniu nagrywania kliknij przycisk Odtwarzanie i wybierz nagrany przed chwilą plik. Powinno się rozpocząć odtwarzanie. Zwróć uwagę, że poprzednie miejsca docelowe znaczników AR pojawiają się w chwili dotykania aplikacji.

To wszystko, co trzeba będzie kodować w ramach tego ćwiczenia.

6. Gratulacje

Gratulacje. To już koniec tego ćwiczenia z programowania. Przypomnijmy Twoje postępy w tym ćwiczeniu z programowania:

  • Utworzysz i uruchomisz przykładową aplikację ARCore Hello AR Java.
  • Dodaliśmy do aplikacji przycisk Nagraj, który umożliwia zapisanie sesji AR w pliku MP4
  • Dodano do aplikacji przycisk odtwarzania do odtwarzania sesji AR z pliku MP4
  • Dodano nową funkcję zapisywania w pliku MP4 reklam zakotwiczonych utworzonych przez użytkownika w pliku MP4.

Świetnie Ci poszło podczas ćwiczeń z programowania?

Tak Nie

Czy udało Ci się dowiedzieć czegoś przydatnego podczas tego ćwiczenia z programowania?

Tak Nie

Czy udało Ci się ukończyć tworzenie aplikacji w ramach tego ćwiczenia z programowania?

Tak Nie
.