مقدمه ای بر ARCore Recording and Playback API

1. مقدمه

امکان ذخیره یک تجربه AR در یک فایل MP4 و پخش از فایل MP4 می تواند هم برای توسعه دهندگان برنامه و هم برای کاربران نهایی مفید باشد.

ویژگی های جدید را از روی میز خود اشکال زدایی و آزمایش کنید

ساده ترین استفاده از ARCore Record & Playback API برای توسعه دهندگان است. روزهایی که مجبور بودید برنامه را روی یک دستگاه آزمایشی بسازید و اجرا کنید، کابل USB را جدا کنید و فقط برای آزمایش یک تغییر کوچک کد راه بروید، گذشته است. اکنون فقط باید یک MP4 را در محیط تست با حرکت مورد انتظار گوشی ضبط کنید و مستقیماً از روی میز خود تست کنید.

ضبط و پخش از دستگاه های مختلف

با API های ضبط و پخش، یک کاربر می تواند یک جلسه را با استفاده از یک دستگاه ضبط کند و دیگری می تواند همان جلسه را در دستگاه دیگری پخش کند. این امکان وجود دارد که یک تجربه AR را با کاربر دیگری به اشتراک بگذارید. امکانات زیادی وجود دارد!

آیا این اولین بار است که یک برنامه ARCore می سازید؟

خیر بله.

چگونه از این کد لبه استفاده خواهید کرد؟

فقط آن را بخوانید آن را بخوانید و تمرینات را کامل کنید

چیزی که خواهی ساخت

در این کد لبه، شما از Recording & Playback API برای ایجاد برنامه ای استفاده خواهید کرد که هم تجربه واقعیت افزوده را در یک فایل MP4 ضبط می کند و هم تجربه را از همان فایل پخش می کند. یاد خواهید گرفت:

  • نحوه استفاده از Recording API برای ذخیره یک جلسه AR در یک فایل MP4.
  • نحوه استفاده از Playback API برای پخش مجدد جلسه AR از یک فایل MP4.
  • نحوه ضبط یک جلسه AR در یک دستگاه و پخش مجدد آن در دستگاه دیگر.

آنچه شما نیاز دارید

در این لبه کد، برنامه Hello AR Java را که با ARCore Android SDK ساخته شده است، اصلاح خواهید کرد. برای پیگیری به سخت افزار و نرم افزار خاصی نیاز دارید.

الزامات سخت افزاری

الزامات نرم افزاری

همچنین برای بهترین نتایج باید درک اولیه ای از 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 را اجرا کنید و روی Open an Android Studio موجود پروژه کلیک کنید.

android-studio-open-projects

در پنجره گفتگوی ایجاد شده، arcore-android-sdk-1.24/samples/hello_ar_java را انتخاب کرده و روی Open کلیک کنید.

منتظر بمانید تا Android Studio همگام سازی پروژه را تمام کند. اگر جزء گم شده باشد، ممکن است وارد کردن پروژه با پیام های خطا با شکست مواجه شود. قبل از ادامه این مشکلات را برطرف کنید.

برنامه نمونه را اجرا کنید

  1. یک دستگاه پشتیبانی شده از ARCore را به دستگاه توسعه خود وصل کنید.
  2. اگر دستگاه به درستی شناسایی شود، باید نام دستگاه را در Android Studio نشان دهید. android-studio-pixel-5.png
  3. روی دکمه Run کلیک کنید یا Run > Run 'app' را انتخاب کنید تا 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 برای دکمه ضبط

قبل از اجرای ضبط، یک دکمه روی رابط کاربری اضافه کنید تا کاربر بتواند به ARCore اطلاع دهد که چه زمانی ضبط را شروع یا متوقف کند.

در پانل پروژه، فایل app/res/layout/activity_main.xml را باز کنید.

activity_main-xml-location-in-project

به طور پیش‌فرض، Android Studio پس از باز کردن فایل app/res/layout/activity_main.xml از نمای طراحی استفاده می‌کند. روی دکمه Code در گوشه سمت راست بالای برگه کلیک کنید تا به نمای کد بروید.

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() خطا را برطرف خواهید کرد. چند قدم بعدی

تغییر متن روی دکمه بر اساس وضعیت

دکمه Record در واقع هم ضبط و هم توقف را انجام می دهد. هنگامی که برنامه در حال ضبط داده نیست، باید کلمه "Record" را نمایش دهد. هنگامی که برنامه در حال ضبط داده است، دکمه باید تغییر کند تا کلمه "Stop" نمایش داده شود.

برای دادن این قابلیت به دکمه، برنامه باید وضعیت فعلی خود را بداند. کد زیر یک فهرست جدید به نام AppState ایجاد می کند تا وضعیت کاری برنامه را نشان دهد و تغییرات وضعیت خاص را از طریق یک متغیر عضو خصوصی به نام appState ردیابی می کند. آن را در ابتدای کلاس HelloArActivity به HelloArActivity.java اضافه کنید.

  // 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() برای تغییر رابط کاربری دکمه فراخوانی می کند. کد زیر را در کلاس 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();
  }

برنامه را برای شروع ضبط فعال کنید

برای شروع ضبط در ARCore فقط باید دو کار انجام دهید:

  1. URI فایل ضبط شده را در یک شی RecordingConfig مشخص کنید.
  2. session.startRecording را با شی RecordingConfig فراخوانی کنید

بقیه فقط کد دیگ بخار است: پیکربندی، ورود به سیستم و بررسی صحت.

یک تابع جدید به نام 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 طراحی کنید

توابع مربوط به ذخیره سازی در این کد لبه با پیروی از الزامات فضای ذخیره سازی جدید اندروید 11 طراحی شده اند.

تغییرات کوچکی در فایل app/build.gradle ایجاد کنید تا Android 11 را هدف قرار دهید. در پانل Android Studio Project، این فایل در زیر گره Gradle Scripts مرتبط با ماژول برنامه قرار دارد.

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

مدیریت مجوزهای ذخیره سازی

اگر از دستگاه اندروید 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" />

برای درخواست مجوزهای WRITE_EXTERNAL_STORAGE در طول زمان اجرا، یک تابع کمکی به نام checkAndRequestStoragePermission() در HelloArActivity.java اضافه کنید.

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

ضبط از دستگاه مورد نظر

وقت آن رسیده است که ببینید تا به حال چه چیزی ساخته اید. دستگاه تلفن همراه خود را به دستگاه توسعه خود متصل کنید و روی Run در Android Studio کلیک کنید.

شما باید یک دکمه قرمز رنگ ضبط را در سمت چپ پایین صفحه ببینید. با ضربه زدن روی آن باید متن را به Stop تغییر دهید. برای ضبط یک جلسه، دستگاه خود را به اطراف حرکت دهید و هنگامی که می‌خواهید ضبط را کامل کنید، روی دکمه توقف کلیک کنید. این باید یک فایل جدید به نام 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 برای فهرست کردن و واکشی فایل‌ها از دستگاه Android آمده است:

  • adb shell ls '$EXTERNAL_STORAGE/Movies/*' برای نمایش فایل‌های فهرست فیلم‌ها در حافظه خارجی دستگاه
  • adb pull /absolute_path_from_previous_adb_shell_ls/arcore-xxxxxxxx_xxxxxx.mp4 تا فایل را از دستگاه به دستگاه توسعه کپی کنید.

این یک نمونه خروجی پس از استفاده از این دو دستور (از 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 برای دکمه پخش

قبل از اجرای پخش، دکمه‌ای را روی رابط کاربری اضافه کنید تا کاربر بتواند به ARCore اطلاع دهد که چه زمانی باید شروع شود و پخش جلسه متوقف شود.

در پانل پروژه ، فایل app/res/layout/activity_main.xml را باز کنید.

activity_main-xml-location-in-project

در activity_main.xml ، کد زیر را قبل از تگ بسته شدن اضافه کنید تا دکمه Playback جدید ایجاد شود و کنترل کننده رویداد آن را روی روشی به نام 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 و همه توابع موجود را که appState به عنوان یک آرگومان برای رسیدگی به این موضوع در نظر می گیرند، به روز کنید.

Playingback به فهرست AppState در HelloArActivity.java اضافه کنید:

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

اگر دکمه ضبط در حین پخش همچنان روی صفحه باشد، کاربر ممکن است به طور تصادفی روی آن کلیک کند. برای جلوگیری از این امر، دکمه ضبط را در حین پخش مخفی کنید. به این ترتیب شما نیازی به مدیریت حالت Playingback در onClickRecord() ندارید.

تابع updateRecordButton() در HelloArActivity.java تغییر دهید تا زمانی که برنامه در حالت Playingback است، دکمه Record پنهان شود.

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

به طور مشابه، هنگامی که کاربر در حال ضبط یک جلسه است، دکمه Playback را پنهان کنید و زمانی که کاربر به طور فعال یک جلسه را پخش می کند، آن را به حالت "Stop" تغییر دهید. به این ترتیب، آنها می توانند یک پخش را بدون اینکه منتظر بمانند تا خود به خود تکمیل شود، متوقف کنند.

یک تابع 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، انتخاب فایل در انتخابگر فایل سیستم در یک فعالیت دیگر انجام می شود. این از طریق چارچوب دسترسی به فضای ذخیره سازی ( SAF ) است. هنگامی که کاربر یک فایل را انتخاب کرد، برنامه یک تماس پاسخ به نام onActivityResult() دریافت می کند. شما پخش واقعی را در داخل این تابع تماس آغاز خواهید کرد.

در 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 برای پخش یک فایل MP4 به سه تماس API نیاز دارد:

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

در HelloArActivity.java ، تابع 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;
  }

برنامه را برای توقف پخش فعال کنید

یک تابع به نام 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() را در رشته رابط کاربری فراخوانی کنید.

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

پخش از دستگاه مورد نظر

وقت آن رسیده است که ببینید تا به حال چه چیزی ساخته اید. دستگاه تلفن همراه خود را به دستگاه توسعه خود متصل کنید و روی Run در Android Studio کلیک کنید.

هنگامی که برنامه راه اندازی شد، باید صفحه ای با دکمه قرمز ضبط در سمت چپ و یک دکمه سبز رنگ پخش در سمت راست مشاهده کنید.

playback-button.png

روی دکمه پخش ضربه بزنید و یکی از فایل های MP4 را که به تازگی ضبط کرده اید انتخاب کنید. اگر هیچ نام فایلی را نمی بینید که با arcore- شروع می شود، شاید دستگاه شما پوشه Movies را نشان نمی دهد. در این مورد، با استفاده از منوی گوشه بالا سمت چپ، به پوشه Phone model > Movies بروید. همچنین ممکن است لازم باشد گزینه Show interior storage را فعال کنید تا پوشه مدل گوشی نمایان شود.

show-internal-storage-button.png

nativate-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 به‌روزرسانی می‌شود.

اگر هنوز جلسه ARCore را ضبط می کنید، ژست یک Anchor را در فریمی که ایجاد می شود، ضبط کنید.

تابع 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);
    }
  }

در تابع onDrawFrame() در HelloArActivity.java createRecordedAnchors() فراخوانی کنید.

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

تست بر روی دستگاه مورد نظر

دستگاه تلفن همراه خود را به دستگاه توسعه خود متصل کنید و روی Run در Android Studio کلیک کنید.

ابتدا روی دکمه Record ضربه بزنید تا یک جلسه ضبط شود. در حین ضبط، روی هواپیماهای شناسایی شده ضربه بزنید تا چند نشانگر AR قرار دهید.

پس از توقف ضبط، روی دکمه پخش ضربه بزنید و فایلی را که به تازگی ضبط کرده اید انتخاب کنید. پخش باید شروع شود. توجه داشته باشید که چگونه مکان‌های نشانگر AR قبلی شما درست زمانی که روی برنامه ضربه می‌زدید ظاهر می‌شوند.

این تمام کدنویسی است که باید برای این کد لبه انجام دهید.

6. تبریک می گویم

تبریک می گویم، شما به پایان این کد لبه رسیده اید! بیایید به کارهایی که در این کد لبه انجام داده اید نگاه کنیم:

  • نمونه ARCore Hello AR Java را ساخته و اجرا کنید.
  • یک دکمه ضبط به برنامه اضافه شد تا یک جلسه AR در یک فایل MP4 ذخیره شود
  • یک دکمه پخش به برنامه اضافه شد تا یک جلسه AR از یک فایل MP4 پخش شود
  • یک ویژگی جدید برای ذخیره لنگرهای ایجاد شده توسط کاربر در MP4 برای پخش اضافه شده است

آیا از انجام این کد لبه لذت بردید؟

بله خیر

آیا در انجام این کد لبه چیز مفیدی یاد گرفتید؟

بله خیر

آیا ساختن برنامه را در این کد لبه کامل کردید؟

بله خیر