ข้อมูลเบื้องต้นเกี่ยวกับ ARCore Recording and Playback API

1. บทนำ

การบันทึกประสบการณ์ AR เป็นไฟล์ MP4 และเล่นจากไฟล์ MP4 ได้จะเป็นประโยชน์ทั้งต่อนักพัฒนาแอปและผู้ใช้ปลายทาง

แก้ไขข้อบกพร่องและทดสอบฟีเจอร์ใหม่จากโต๊ะทำงาน

การใช้งาน ARCore Record & Playback API ที่ตรงไปตรงมาที่สุดคือสำหรับนักพัฒนาแอป ในอดีต คุณต้องสร้างและเรียกใช้แอปในอุปกรณ์ทดสอบ ถอดสาย USB และเดินไปมาเพื่อทดสอบการเปลี่ยนแปลงโค้ดเล็กๆ น้อยๆ ตอนนี้คุณเพียงแค่ต้องบันทึก MP4 ในสภาพแวดล้อมการทดสอบโดยให้โทรศัพท์เคลื่อนไหวตามที่คาดไว้ และทดสอบจากโต๊ะทำงานได้เลย

บันทึกและเล่นจากอุปกรณ์ต่างๆ

Recording and Playback API ช่วยให้ผู้ใช้คนหนึ่งบันทึกเซสชันโดยใช้อุปกรณ์เครื่องหนึ่ง และผู้ใช้คนอื่นสามารถเล่นเซสชันเดียวกันในอุปกรณ์เครื่องอื่นได้ คุณแชร์ประสบการณ์ AR กับผู้ใช้รายอื่นได้ ซึ่งมีตัวเลือกมากมาย

นี่เป็นครั้งแรกที่คุณสร้างแอป ARCore ใช่ไหม

ไม่ ใช่

คุณจะใช้ Codelab นี้อย่างไร

อ่านอย่างเดียว อ่านและทำแบบฝึกหัดให้เสร็จ

สิ่งที่คุณจะสร้าง

ใน Codelab นี้ คุณจะได้ใช้ API การบันทึกและการเล่นเพื่อสร้างแอปที่ทั้งบันทึกประสบการณ์ AR ลงในไฟล์ MP4 และเล่นประสบการณ์ดังกล่าวจากไฟล์เดียวกัน คุณจะได้เรียนรู้:

  • วิธีใช้ Recording API เพื่อบันทึกเซสชัน AR ลงในไฟล์ MP4
  • วิธีใช้ Playback API เพื่อเล่นเซสชัน AR ซ้ำจากไฟล์ MP4
  • วิธีบันทึกเซสชัน AR ในอุปกรณ์เครื่องหนึ่งและเล่นซ้ำในอุปกรณ์อีกเครื่อง

สิ่งที่คุณต้องมี

ใน Codelab นี้ คุณจะได้แก้ไขแอป Hello AR Java ซึ่งสร้างด้วย ARCore Android SDK คุณจะต้องมีฮาร์ดแวร์และซอฟต์แวร์ที่เฉพาะเจาะจงเพื่อทำตาม

ข้อกำหนดเกี่ยวกับฮาร์ดแวร์

  • อุปกรณ์ที่รองรับ ARCore ซึ่งเปิดตัวเลือกสำหรับนักพัฒนาแอป และเปิดใช้การแก้ไขข้อบกพร่อง USB โดยเชื่อมต่อผ่านสาย USB กับคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์
  • คอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์ที่คุณใช้ Android Studio
  • การเข้าถึงอินเทอร์เน็ตเพื่อดาวน์โหลดไลบรารีในระหว่างการพัฒนา

ข้อกำหนดของซอฟต์แวร์

นอกจากนี้ คุณควรมีความเข้าใจพื้นฐานเกี่ยวกับ ARCore เพื่อให้ได้ผลลัพธ์ที่ดีที่สุด

2. ตั้งค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์

เริ่มต้นด้วยการตั้งค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์

ดาวน์โหลด ARCore Android SDK

คลิก เพื่อดาวน์โหลด SDK

คลายซิป ARCore Android SDK

เมื่อดาวน์โหลด Android SDK ลงในเครื่องแล้ว ให้แตกไฟล์และไปที่ไดเรกทอรี arcore-android-sdk-1.24/samples/hello_ar_java นี่คือไดเรกทอรีรากของแอปที่คุณจะใช้

hello-ar-java-extracted

โหลด Hello AR Java ลงใน Android Studio

เปิด Android Studio แล้วคลิกเปิดโปรเจ็กต์ Android Studio ที่มีอยู่

android-studio-open-projects

ในกล่องโต้ตอบที่ปรากฏขึ้น ให้เลือก arcore-android-sdk-1.24/samples/hello_ar_java แล้วคลิกเปิด

รอให้ Android Studio ซิงค์โปรเจ็กต์เสร็จ หากมีคอมโพเนนต์ขาดหายไป การนำเข้าโปรเจ็กต์อาจล้มเหลวพร้อมข้อความแสดงข้อผิดพลาด โปรดแก้ไขปัญหาเหล่านี้ก่อนดำเนินการต่อ

เรียกใช้แอปตัวอย่าง

  1. เชื่อมต่ออุปกรณ์ที่รองรับ ARCore กับคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์
  2. หากระบบรู้จักอุปกรณ์อย่างถูกต้อง คุณควรเห็นชื่ออุปกรณ์ปรากฏใน Android Studio android-studio-pixel-5.png
  3. คลิกปุ่มเรียกใช้ หรือเลือกเรียกใช้ > เรียกใช้ "แอป" เพื่อให้ Android Studio ติดตั้งและเปิดแอปในอุปกรณ์ android-studio-run-button.png
  4. คุณจะเห็นข้อความแจ้งที่ขอสิทธิ์ในการถ่ายภาพและบันทึกวิดีโอ เลือกขณะใช้แอปนี้เพื่อให้สิทธิ์เข้าถึงกล้องแก่แอป จากนั้นคุณจะเห็นสภาพแวดล้อมจริงบนหน้าจอของอุปกรณ์ hello-ar-java-permission
  5. ขยับอุปกรณ์ในแนวนอนเพื่อสแกนหาเครื่องบิน
  6. ตารางกริดสีขาวจะปรากฏขึ้นเมื่อแอปตรวจพบระนาบ แตะเพื่อวางเครื่องหมายบนเครื่องบินนั้น ตำแหน่งโฆษณา AR

สิ่งที่คุณทำในขั้นตอนนี้

  • ตั้งค่าโปรเจ็กต์ Hello AR Java
  • สร้างและเรียกใช้แอปตัวอย่างในอุปกรณ์ที่รองรับ ARCore

จากนั้นคุณจะบันทึกเซสชัน AR เป็นไฟล์ MP4

3. บันทึกเซสชัน ARCore เป็นไฟล์ MP4

เราจะเพิ่มฟีเจอร์การบันทึกในขั้นตอนนี้ โดยประกอบด้วย

  • ปุ่มเพื่อเริ่มหรือหยุดการบันทึก
  • ฟังก์ชันการจัดเก็บข้อมูลเพื่อบันทึกไฟล์ MP4 ในอุปกรณ์
  • การเรียกเพื่อเริ่มหรือหยุดการบันทึกเซสชัน ARCore

เพิ่ม UI สำหรับปุ่มบันทึก

ก่อนที่จะติดตั้งใช้งานการบันทึก ให้เพิ่มปุ่มใน UI เพื่อให้ผู้ใช้แจ้ง ARCore ได้ว่าจะเริ่มหรือหยุดบันทึกเมื่อใด

เปิดไฟล์ app/res/layout/activity_main.xml ในแผงโปรเจ็กต์

activity_main-xml-location-in-project

โดยค่าเริ่มต้น Android Studio จะใช้มุมมองการออกแบบหลังจากที่คุณเปิดไฟล์ app/res/layout/activity_main.xml คลิกปุ่มโค้ดที่มุมขวาบนของแท็บเพื่อเปลี่ยนเป็นมุมมองโค้ด

swith-to-the-code-view.png

ใน activity_main.xml ให้เพิ่มโค้ดต่อไปนี้ก่อนแท็กปิดเพื่อสร้างปุ่ม Record ใหม่ และตั้งค่าตัวแฮนเดิลเหตุการณ์เป็นเมธอดที่ชื่อ 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" />

หลังจากเพิ่มโค้ดข้างต้นแล้ว ระบบอาจแสดงข้อผิดพลาดชั่วคราว: Corresponding method handler 'public void onClickRecord(android.view.View)' not found" กรณีนี้เป็นสิ่งที่คาดว่าจะเกิดอยู่แล้ว คุณจะแก้ไขข้อผิดพลาดได้โดยสร้างฟังก์ชัน onClickRecord() ในอีกไม่กี่ขั้นตอนข้างหน้า

เปลี่ยนข้อความในปุ่มตามสถานะ

ปุ่มบันทึกจะจัดการทั้งการบันทึกและการหยุด เมื่อแอปไม่ได้บันทึกข้อมูล แอปควรแสดงคำว่า "บันทึก" เมื่อแอปบันทึกข้อมูล ปุ่มควรเปลี่ยนไปแสดงคำว่า "หยุด"

แอปต้องทราบสถานะปัจจุบันของปุ่มเพื่อให้ปุ่มมีฟังก์ชันการทำงานนี้ โค้ดต่อไปนี้สร้างการแจงนับใหม่ชื่อ AppState เพื่อแสดงสถานะการทำงานของแอป และติดตามการเปลี่ยนแปลงสถานะที่เฉพาะเจาะจงผ่านตัวแปรสมาชิกส่วนตัวที่ชื่อ appState เพิ่มลงใน HelloArActivity.java ที่จุดเริ่มต้นของคลาส HelloArActivity

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

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

ตอนนี้คุณสามารถติดตามสถานะภายในของแอปได้แล้ว ให้สร้างฟังก์ชันชื่อ updateRecordButton() ซึ่งจะเปลี่ยนข้อความของปุ่มตามสถานะปัจจุบันของแอป เพิ่มโค้ดต่อไปนี้ภายในคลาส HelloArActivity ใน 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;
    }
  }

จากนั้นสร้างเมธอด onClickRecord() ที่ตรวจสอบสถานะของแอป เปลี่ยนเป็นสถานะถัดไป และเรียกใช้ updateRecordButton() เพื่อเปลี่ยน UI ของปุ่ม เพิ่มโค้ดต่อไปนี้ภายในคลาส HelloArActivity ใน 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();
  }

เปิดใช้แอปเพื่อเริ่มบันทึก

คุณต้องทำเพียง 2 สิ่งเพื่อเริ่มบันทึกใน ARCore

  1. ระบุ URI ของไฟล์บันทึกในออบเจ็กต์ RecordingConfig
  2. โทรหา session.startRecording ด้วยออบเจ็กต์ RecordingConfig

ส่วนที่เหลือเป็นเพียงโค้ด Boilerplate ได้แก่ การกำหนดค่า การบันทึก และการตรวจสอบความถูกต้อง

สร้างฟังก์ชันใหม่ชื่อ startRecording() ซึ่งจะบันทึกข้อมูลและจัดเก็บไว้ใน URI ของ MP4 เพิ่มโค้ดต่อไปนี้ภายในคลาส HelloArActivity ใน 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;
  }

หากต้องการหยุดชั่วคราวและกลับมาใช้เซสชัน ARCore ต่ออย่างปลอดภัย ให้สร้าง pauseARCoreSession() และ resumeARCoreSession() ใน 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;
  }

เปิดใช้แอปเพื่อหยุดบันทึก

สร้างฟังก์ชันชื่อ stopRecording() ใน HelloArActivity.java เพื่อหยุดไม่ให้แอปบันทึกข้อมูลใหม่ ฟังก์ชันนี้จะเรียกใช้ session.stopRecording() และส่งข้อผิดพลาดไปยังบันทึกของคอนโซลหากแอปหยุดบันทึกไม่ได้

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

ออกแบบที่เก็บไฟล์โดยใช้พื้นที่เก็บข้อมูลที่จำกัดขอบเขตของ Android 11

ฟังก์ชันที่เกี่ยวข้องกับพื้นที่เก็บข้อมูลใน Codelab นี้ได้รับการออกแบบตามข้อกำหนดใหม่ของพื้นที่เก็บข้อมูลที่จำกัดขอบเขตใน Android 11

ทำการเปลี่ยนแปลงเล็กน้อยในapp/build.gradleเพื่อกำหนดเป้าหมายเป็น Android 11 ในแผงโปรเจ็กต์ของ Android Studio ไฟล์นี้จะอยู่ใต้โหนดสคริปต์ Gradle ที่เชื่อมโยงกับโมดูล app

app-build.gradle.png

เปลี่ยน compileSdkVersion และ targetSdkVersion เป็น 30

    compileSdkVersion 30
    defaultConfig {
      targetSdkVersion 30
    }

สำหรับการบันทึก ให้ใช้ Android MediaStore API เพื่อสร้างไฟล์ MP4 ในไดเรกทอรีภาพยนตร์ที่แชร์

สร้างฟังก์ชันชื่อ createMp4File() ใน 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;
  }

จัดการสิทธิ์เข้าถึงพื้นที่เก็บข้อมูล

หากใช้อุปกรณ์ Android 11 คุณจะเริ่มทดสอบโค้ดได้ หากต้องการรองรับอุปกรณ์ Android 10 หรือต่ำกว่า คุณจะต้องให้สิทธิ์พื้นที่เก็บข้อมูลของแอปเพื่อบันทึกข้อมูลลงในระบบไฟล์ของอุปกรณ์เป้าหมาย

ใน AndroidManifest.xml ให้ประกาศว่าแอปต้องมีสิทธิ์อ่านและเขียนพื้นที่เก็บข้อมูลก่อน Android 11 (ระดับ 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" />

เพิ่มฟังก์ชันตัวช่วยที่ชื่อ checkAndRequestStoragePermission() ใน HelloArActivity.java เพื่อขอสิทธิ์ WRITE_EXTERNAL_STORAGE ในระหว่างรันไทม์

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

หากใช้ระดับ API 29 หรือต่ำกว่า ให้เพิ่มการตรวจสอบสิทธิ์เข้าถึงพื้นที่เก็บข้อมูลที่ด้านบนของ createMp4File() และออกจากฟังก์ชันก่อนเวลาหากแอปไม่มีสิทธิ์ที่ถูกต้อง ระดับ API 30 (Android 11) ไม่ต้องใช้สิทธิ์เข้าถึงพื้นที่เก็บข้อมูลเพื่อเข้าถึงไฟล์ใน 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 ...
  }

บันทึกจากอุปกรณ์เป้าหมาย

ถึงเวลาดูสิ่งที่คุณสร้างมาจนถึงตอนนี้แล้ว เชื่อมต่ออุปกรณ์เคลื่อนที่กับคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์ แล้วคลิกเรียกใช้ใน Android Studio

คุณควรเห็นปุ่มบันทึกสีแดงที่ด้านล่างซ้ายของหน้าจอ การแตะปุ่มนี้จะเปลี่ยนข้อความเป็นหยุด ขยับอุปกรณ์ไปรอบๆ เพื่อบันทึกเซสชัน แล้วคลิกปุ่มหยุดเมื่อต้องการบันทึกให้เสร็จสมบูรณ์ การดำเนินการนี้จะบันทึกไฟล์ใหม่ชื่อ arcore-xxxxxx_xxxxxx.mp4 ไว้ในพื้นที่เก็บข้อมูลภายนอกของอุปกรณ์

record-button.png

ตอนนี้คุณควรมีarcore-xxxxxx_xxxxxx.mp4ไฟล์ใหม่ในที่เก็บข้อมูลภายนอกของอุปกรณ์ ในอุปกรณ์ Pixel 5 เส้นทางคือ /storage/emulated/0/Movies/ คุณดูเส้นทางได้ในหน้าต่าง Logcat หลังจากเริ่มบันทึก

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

ดูการบันทึก

คุณสามารถใช้แอประบบไฟล์ เช่น Files by Google เพื่อดูการบันทึก หรือคัดลอกไปยังคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์ ด้านล่างนี้คือคำสั่ง adb 2 รายการสำหรับแสดงและดึงข้อมูลไฟล์จากอุปกรณ์ Android

  • adb shell ls '$EXTERNAL_STORAGE/Movies/*' เพื่อแสดงไฟล์ในไดเรกทอรีภาพยนตร์ในที่เก็บข้อมูลภายนอกบนอุปกรณ์
  • adb pull /absolute_path_from_previous_adb_shell_ls/arcore-xxxxxxxx_xxxxxx.mp4 เพื่อคัดลอกไฟล์จากอุปกรณ์ไปยังคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์

นี่คือตัวอย่างเอาต์พุตหลังจากใช้คำสั่ง 2 รายการนี้ (จาก 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

สิ่งที่คุณทำในขั้นตอนนี้

  • เพิ่มปุ่มเพื่อเริ่มและหยุดการบันทึก
  • ฟังก์ชันที่ใช้เพื่อเริ่มและหยุดการบันทึก
  • ทดสอบแอปในอุปกรณ์
  • คัดลอก MP4 ที่บันทึกไว้ไปยังเครื่องของคุณและยืนยันแล้ว

จากนั้นคุณจะเล่นเซสชัน AR จากไฟล์ MP4

4. เล่นเซสชัน ARCore จากไฟล์ MP4

ตอนนี้คุณจะมีปุ่มบันทึกและไฟล์ MP4 บางไฟล์ที่มีเซสชันที่บันทึกไว้ ตอนนี้คุณจะเล่นไฟล์เหล่านั้นได้โดยใช้ ARCore Playback API

เพิ่ม UI สำหรับปุ่มเล่น

ก่อนที่จะใช้การเล่น ให้เพิ่มปุ่มใน UI เพื่อให้ผู้ใช้แจ้ง ARCore ได้ว่าควรเริ่มและหยุดเล่นเซสชันเมื่อใด

เปิดไฟล์ app/res/layout/activity_main.xml ในแผงโปรเจ็กต์

activity_main-xml-location-in-project

ใน activity_main.xml ให้เพิ่มโค้ดด้านล่างก่อนแท็กปิดเพื่อสร้างปุ่มเล่นใหม่ และตั้งค่าตัวแฮนเดิลเหตุการณ์เป็นเมธอดที่ชื่อ onClickPlayback() ปุ่มนี้จะคล้ายกับปุ่มบันทึกและจะแสดงที่ด้านขวาของหน้าจอ

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

ปุ่มอัปเดตระหว่างการเล่น

ตอนนี้แอปมีสถานะใหม่ที่เรียกว่า Playingback อัปเดต AppState enum และฟังก์ชันที่มีอยู่ทั้งหมดที่ใช้ appState เป็นอาร์กิวเมนต์เพื่อจัดการปัญหานี้

เพิ่ม Playingback ลงใน enum AppState ใน HelloArActivity.java ดังนี้

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

หากปุ่มบันทึกยังคงอยู่บนหน้าจอระหว่างการเล่น ผู้ใช้อาจคลิกปุ่มนี้โดยไม่ตั้งใจ หากต้องการหลีกเลี่ยงปัญหานี้ ให้ซ่อนปุ่มบันทึกระหว่างการเล่น วิธีนี้ช่วยให้คุณไม่ต้องจัดการสถานะสำหรับ Playingback ใน onClickRecord()

แก้ไขฟังก์ชัน updateRecordButton() ใน HelloArActivity.java เพื่อซ่อนปุ่มบันทึกเมื่อแอปอยู่ในสถานะ 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;
    }
  }

ในทำนองเดียวกัน ให้ซ่อนปุ่มเล่นเมื่อผู้ใช้บันทึกเซสชัน และเปลี่ยนปุ่มเป็น "หยุด" เมื่อผู้ใช้เล่นเซสชันอยู่ วิธีนี้จะช่วยให้ผู้ใช้หยุดการเล่นได้โดยไม่ต้องรอให้เล่นจนจบ

วิธีเพิ่มupdatePlaybackButton()ฟังก์ชันใน 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;
    }
  }

สุดท้าย ให้อัปเดต onClickRecord() เพื่อเรียกใช้ updatePlaybackButton() เพิ่มบรรทัดต่อไปนี้ใน HelloArActivity.java

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

เลือกไฟล์ที่มีปุ่มเล่น

เมื่อแตะ ปุ่มเล่นควรอนุญาตให้ผู้ใช้เลือกไฟล์ที่จะเล่น ใน Android การเลือกไฟล์จะได้รับการจัดการในเครื่องมือเลือกไฟล์ของระบบในกิจกรรมอื่น ซึ่งทำได้ผ่าน Storage Access Framework (SAF) เมื่อผู้ใช้เลือกไฟล์แล้ว แอปจะได้รับการเรียกกลับที่ชื่อ onActivityResult() คุณจะเริ่มการเล่นจริงภายในฟังก์ชัน Callback นี้

ใน HelloArActivity.java ให้สร้างฟังก์ชัน onClickPlayback() เพื่อเลือกไฟล์และหยุดเล่น

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

ใน HelloArActivity.java ให้สร้างฟังก์ชัน selectFileToPlayback() ที่เลือกไฟล์จากอุปกรณ์ หากต้องการเลือกไฟล์จากระบบไฟล์ Android ให้ใช้ ACTION_OPEN_DOCUMENT Intent

// 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 เป็นค่าคงที่ที่ใช้ระบุคำขอนี้ คุณกำหนดได้โดยใช้ค่าตัวยึดตำแหน่งใดก็ได้ภายใน HelloArActivity ใน HelloArActivity.java ดังนี้

  private int REQUEST_MP4_SELECTOR = 1;

ลบล้างฟังก์ชัน onActivityResult() ใน HelloArActivity.java เพื่อจัดการการเรียกกลับจากเครื่องมือเลือกไฟล์

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

เปิดใช้แอปเพื่อเริ่มเล่น

เซสชัน ARCore ต้องมีการเรียก API 3 ครั้งเพื่อเล่นไฟล์ MP4

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

สร้างฟังก์ชัน startPlayingback() ใน HelloArActivity.java

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

เปิดใช้แอปเพื่อหยุดการเล่น

สร้างฟังก์ชันชื่อ stopPlayingback() ใน HelloArActivity.java เพื่อจัดการการเปลี่ยนแปลงสถานะของแอปหลังจากเหตุการณ์ต่อไปนี้

  1. ผู้ใช้หยุดการเล่น MP4
  2. การเล่น MP4 เสร็จสมบูรณ์แล้ว

หากผู้ใช้หยุดการเล่น แอปควรกลับไปอยู่ในสถานะเดียวกับตอนที่ผู้ใช้เปิดแอปครั้งแรก

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

นอกจากนี้ การเล่นยังหยุดลงได้โดยอัตโนมัติหลังจากที่เพลเยอร์เล่นไฟล์ MP4 จนจบ ในกรณีนี้ stopPlayingback() ควรเปลี่ยนสถานะของแอปกลับเป็น Idle ใน onDrawFrame() ให้ตรวจสอบ PlaybackStatus หากเป็น FINISHED ให้เรียกใช้ฟังก์ชัน stopPlayingback() ในเธรด UI

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

เล่นจากอุปกรณ์เป้าหมาย

ถึงเวลาดูสิ่งที่คุณสร้างมาจนถึงตอนนี้แล้ว เชื่อมต่ออุปกรณ์เคลื่อนที่กับคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์ แล้วคลิกเรียกใช้ใน Android Studio

เมื่อเปิดแอป คุณจะเห็นหน้าจอที่มีปุ่มบันทึกสีแดงทางด้านซ้ายและปุ่มเล่นสีเขียวทางด้านขวา

playback-button.png

แตะปุ่มเล่น แล้วเลือกไฟล์ MP4 ไฟล์ใดไฟล์หนึ่งที่คุณเพิ่งบันทึก หากไม่เห็นชื่อไฟล์ที่ขึ้นต้นด้วย arcore- แสดงว่าอุปกรณ์อาจไม่แสดงโฟลเดอร์ Movies ในกรณีนี้ ให้ไปที่โฟลเดอร์รุ่นโทรศัพท์ > ภาพยนตร์โดยใช้เมนูที่มุมซ้ายบน นอกจากนี้ คุณอาจต้องเปิดใช้ตัวเลือกแสดงที่จัดเก็บข้อมูลภายในเพื่อแสดงโฟลเดอร์รุ่นโทรศัพท์

show-internal-storage-button.pngnativate-to-movies-file-picker.jpg

แตะชื่อไฟล์บนหน้าจอเพื่อเลือกไฟล์ MP4 แอปควรเล่นไฟล์ MP4

playback-stop-button.png

ความแตกต่างระหว่างการเล่นเซสชันย้อนหลังกับการเล่นวิดีโอทั่วไปคือคุณสามารถโต้ตอบกับเซสชันที่บันทึกไว้ได้ แตะระนาบที่ตรวจพบเพื่อวางเครื่องหมายบนหน้าจอ

ตำแหน่งการเล่น

สิ่งที่คุณทำในขั้นตอนนี้

  • เพิ่มปุ่มเพื่อเริ่มและหยุดการเล่น
  • ใช้ฟังก์ชันเพื่อให้แอปเริ่มและหยุดบันทึก
  • เล่นเซสชัน ARCore ที่บันทึกไว้ก่อนหน้านี้ในอุปกรณ์

5. บันทึกข้อมูลเพิ่มเติมใน MP4

ARCore 1.24 ช่วยให้บันทึกข้อมูลเพิ่มเติมในไฟล์ MP4 ได้ คุณสามารถบันทึกPoseตำแหน่งของออบเจ็กต์ AR แล้วสร้างออบเจ็กต์ AR ในตำแหน่งเดียวกันระหว่างการเล่น

กำหนดค่าแทร็กใหม่เพื่อบันทึก

กำหนดแทร็กใหม่ด้วย UUID และแท็ก MIME ใน 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 ...
  }

อัปเดตรหัสที่มีอยู่เพื่อสร้างออบเจ็กต์ RecordingConfig ด้วยการเรียกใช้ 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 ...
  }

บันทึกท่าทางของจุดยึดระหว่างการบันทึก

ทุกครั้งที่ผู้ใช้แตะระนาบที่ตรวจพบ ระบบจะวางเครื่องหมาย AR บน Anchor ซึ่ง ARCore จะอัปเดตท่าทางของเครื่องหมาย

บันทึกท่าทางของ Anchor ที่เฟรมที่สร้างขึ้น หากคุณยังบันทึกเซสชัน ARCore อยู่

แก้ไขฟังก์ชัน handleTap() ใน 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 ...
  }

เหตุผลที่เราเก็บ Pose ที่สัมพันธ์กับกล้องแทนที่จะเป็น Pose ที่สัมพันธ์กับโลกก็คือจุดเริ่มต้นของโลกในเซสชันการบันทึกและจุดเริ่มต้นของโลกในเซสชันการเล่นไม่เหมือนกัน จุดเริ่มต้นของเซสชันการบันทึกจะเริ่มเมื่อกลับมาใช้เซสชันเป็นครั้งแรก เมื่อมีการเรียกใช้ Session.resume() เป็นครั้งแรก ต้นทางของเซสชันการเล่นจะเริ่มขึ้นเมื่อมีการบันทึกเฟรมแรก เมื่อมีการเรียกใช้ Session.resume() เป็นครั้งแรกหลังจาก Session.startRecording()

การสร้างจุดยึดการเล่น

การสร้าง Anchor ใหม่นั้นทำได้ง่ายๆ เพิ่มฟังก์ชันชื่อ createRecordedAnchors() ใน 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);
    }
  }

เรียกใช้ฟังก์ชัน createRecordedAnchors() ใน onDrawFrame() ใน 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 ...
  }

ทดสอบบนอุปกรณ์เป้าหมาย

เชื่อมต่ออุปกรณ์เคลื่อนที่กับคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์ แล้วคลิกเรียกใช้ใน Android Studio

ก่อนอื่น ให้แตะปุ่มบันทึกเพื่อบันทึกเซสชัน ขณะบันทึก ให้แตะเครื่องบินที่ตรวจพบเพื่อวางเครื่องหมาย AR 2-3 อัน

หลังจากหยุดบันทึกแล้ว ให้แตะปุ่มเล่น แล้วเลือกไฟล์ที่เพิ่งบันทึก การเล่นควรจะเริ่มขึ้น สังเกตว่าตำแหน่งเครื่องหมาย AR ก่อนหน้าจะปรากฏขึ้นทันทีที่คุณแตะแอป

การเขียนโค้ดทั้งหมดที่คุณจะต้องทำสำหรับ Codelab นี้

6. ขอแสดงความยินดี

ขอแสดงความยินดี คุณทำ Codelab นี้เสร็จเรียบร้อยแล้ว มาดูสิ่งที่คุณได้ทำใน Codelab นี้กัน

  • สร้างและเรียกใช้ตัวอย่าง Hello AR Java ของ ARCore
  • เพิ่มปุ่มบันทึกลงในแอปเพื่อบันทึกเซสชัน AR เป็นไฟล์ MP4
  • เพิ่มปุ่มเล่นลงในแอปเพื่อเล่นเซสชัน AR จากไฟล์ MP4
  • เพิ่มฟีเจอร์ใหม่เพื่อบันทึก Anchor ที่ผู้ใช้สร้างไว้ใน MP4 สำหรับการเล่น

คุณสนุกกับการทำ Codelab นี้ไหม

ใช่ ไม่ใช่

คุณได้เรียนรู้สิ่งที่มีประโยชน์ในการทำ Codelab นี้ไหม

ใช่ ไม่ใช่

คุณสร้างแอปในโค้ดแล็บนี้เสร็จแล้วใช่ไหม

ใช่ ไม่