مقدّمة حول واجهة برمجة تطبيقات ARCore لكل من التسجيل والتشغيل

1. مقدمة

قد تكون إمكانية حفظ تجربة الواقع المعزّز في ملف MP4 وتشغيلها من ملف MP4 مفيدة لكل من مطوّري التطبيقات والمستخدمين النهائيين.

تصحيح الأخطاء واختبار الميزات الجديدة من مكتبك

الاستخدام الأكثر وضوحًا لسجلّ ARCore واجهة برمجة تطبيقات التشغيل هذه مخصصة للمطوّرين. انقضت الأيام التي يجب فيها إنشاء التطبيق وتشغيله على جهاز اختباري، وفصل كابل USB، وتجربته فقط لاختبار تغيير بسيط في الرمز. ما عليك الآن سوى تسجيل فيديو بتنسيق MP4 في بيئة الاختبار مع ملاحظة حركة الهاتف المتوقّعة، واختبارها من مكتبك مباشرةً.

سجِّل المحتوى وشغِّله من أجهزة مختلفة

باستخدام واجهتي برمجة التطبيقات للتسجيل والتشغيل، يمكن لمستخدم واحد تسجيل جلسة باستخدام أحد الأجهزة، ويمكن لمستخدم آخر تشغيل الجلسة نفسها على جهاز مختلف. من الممكن مشاركة تجربة الواقع المعزّز مع مستخدم آخر. هناك الكثير من الاحتمالات!

هل هذه هي المرة الأولى التي تنشئ فيها تطبيق ARCore؟

لا. نعم.

كيف ستستخدم هذا الدرس التطبيقي حول الترميز؟

قراءة النص فقط اقرأها وأكمِل التمارين

ما الذي ستقوم ببنائه

في هذا الدرس التطبيقي حول الترميز، ستستخدم زرَّي التسجيل واجهة برمجة تطبيقات التشغيل لإنشاء تطبيق يسجّل تجربة الواقع المعزّز في ملف MP4 ويشغّل التجربة من الملف نفسه. سوف تتعلم ما يلي:

  • كيفية استخدام واجهة برمجة تطبيقات التسجيل لحفظ جلسة الواقع المعزّز في ملف MP4.
  • طريقة استخدام واجهة برمجة تطبيقات التشغيل لإعادة تشغيل جلسة الواقع المعزّز من ملف MP4
  • طريقة تسجيل جلسة الواقع المعزّز على أحد الأجهزة وإعادة تشغيلها على جهاز آخر

المتطلبات

في هذا الدرس التطبيقي حول الترميز، عليك تعديل تطبيق Hello AR Java المصمم باستخدام ARCore Android SDK. ستحتاج إلى أجهزة وبرامج معينة للمتابعة.

متطلبات الأجهزة

  • جهاز متوافق مع ARCore وخيارات المطوّرين فيه وتصحيح أخطاء الجهاز عبر USB، وتوصيله بجهاز التطوير عبر كابل USB.
  • جهاز تطوير يمكنك من خلاله تشغيل "استوديو Android".
  • إمكانية الاتصال بالإنترنت لتنزيل المكتبات أثناء التطوير

متطلبات البرامج

ومن المفترض أيضًا أن تكون على دراية تامة بلعبة ARCore للحصول على أفضل النتائج.

2. إعداد بيئة التطوير

ابدأ بإعداد بيئة التطوير الخاصة بك.

تنزيل حزمة تطوير البرامج (SDK) لنظام التشغيل Android من ARCore

انقر على لتنزيل حزمة SDK.

فك ضغط حزمة تطوير البرامج (SDK) لنظام التشغيل Android من ARCore

بعد تنزيل حزمة تطوير البرامج (SDK) لنظام التشغيل Android على جهازك، يُرجى فك ضغط الملف والانتقال إلى دليل arcore-android-sdk-1.24/samples/hello_ar_java. هذا هو الدليل الجذري للتطبيق الذي ستعمل عليه.

hello-ar-java-extracted

تحميل Hello AR Java في "استوديو Android"

افتح "استوديو Android" وانقر على فتح مشروع حالي على "استوديو Android".

android-studio-open-projects

في نافذة مربّع الحوار الذي يظهر، اختَر arcore-android-sdk-1.24/samples/hello_ar_java وانقر على فتح.

انتظِر إلى أن تنتهي "استوديو Android" من مزامنة المشروع. إذا كان هناك مكون مفقود، فقد يفشل استيراد المشروع مع ظهور رسائل خطأ. يُرجى حلّ هذه المشاكل قبل المتابعة.

تشغيل نموذج التطبيق

  1. عليك توصيل جهاز متوافق مع ARCore بجهاز التطوير.
  2. إذا تم التعرّف على الجهاز بشكل صحيح، من المفترض أن يظهر اسم الجهاز في "استوديو Android". android-studio-pixel-5.png
  3. انقر على الزر "تشغيل" أو حدد تشغيل > شغِّل "التطبيق" لتثبيت "استوديو Android" وتشغيله على الجهاز. android-studio-run-button.png
  4. ستظهر لك رسالة تطلب منك الحصول على إذن لالتقاط صور وتسجيل فيديوهات. اختَر أثناء استخدام هذا التطبيق لمنح التطبيق أذونات الكاميرا. ثم سترى بيئتك الواقعية على شاشة الجهاز. hello-ar-java-permission
  5. حرِّك الجهاز أفقيًا للبحث عن الطائرات.
  6. تظهر شبكة بيضاء عندما يرصد التطبيق طائرة. انقر عليها لوضع محدّد موقع على هذه الطائرة. مرحبًا بموضع الواقع المعزّز

الإجراءات التي اتّخذتها في هذه الخطوة

  • إعداد مشروع Hello AR Java
  • إنشاء نموذج التطبيق وتشغيله على جهاز متوافق مع ARCore

بعد ذلك، ستسجّل جلسة الواقع المعزّز إلى ملف MP4.

3- تسجيل جلسة ARCore إلى ملف MP4

سنضيف ميزة التسجيل في هذه الخطوة. وتتألّف من:

  • زر لبدء التسجيل أو إيقافه
  • تعمل وظائف التخزين لحفظ ملف MP4 على الجهاز.
  • مكالمات لبدء تسجيل جلسة ARCore أو إيقافها

إضافة واجهة مستخدم للزر "تسجيل"

قبل تنفيذ التسجيل، أضِف زرًّا على واجهة المستخدم حتى يتمكّن المستخدم من إعلام ARCore بالوقت المناسب لبدء التسجيل أو إيقافه.

في لوحة المشروع، افتح ملف app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

سيستخدم "استوديو Android" تلقائيًا عرض التصميم بعد فتح ملف 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() لتغيير واجهة المستخدم الخاصة بالزر. أضِف الرمز التالي داخل الفئة 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() لتسجيل البيانات وحفظها في عنوان URL بتنسيق 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;
  }

تصميم تخزين الملفات باستخدام التخزين بنطاق 11 Android

تم تصميم الوظائف المتعلّقة بمساحة التخزين في هذا الدرس التطبيقي حول الترميز وفقًا لمتطلبات مساحة التخزين الجديدة في Android 11.

أدخِل بعض التغييرات الصغيرة في ملف app/build.gradle لاستهداف الإصدار Android 11. في لوحة "مشروع استوديو Android"، يندرج هذا الملف ضمن عقدة 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;
  }

التعامل مع أذونات مساحة التخزين

إذا كنت تستخدم جهاز Android 11، يمكنك بدء اختبار الرمز. للتوافق مع الأجهزة التي تعمل بنظام التشغيل Android 10 أو الإصدارات الأقدم، عليك منح أذونات مساحة تخزين التطبيقات لحفظ البيانات في نظام ملفات الجهاز المستهدَف.

في AndroidManifest.xml، يمكنك توضيح أنّ التطبيق يحتاج إلى أذونات للقراءة والكتابة في مساحة التخزين قبل نظام Android 11 (المستوى 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;
  }

إذا كنت تستخدم المستوى 29 من واجهة برمجة التطبيقات أو مستوى أقدم، يمكنك التحقّق من أذونات التخزين في أعلى createMp4File() والخروج من الوظيفة مبكرًا إذا لم يحصل التطبيق على الأذونات الصحيحة. لا يتطلّب المستوى 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".

من المفترض أن يظهر لك زر تسجيل أحمر في أسفل الجانب الأيمن من الشاشة. ومن المفترض أن يؤدي النقر عليه إلى تغيير النص إلى إيقاف. حرِّك جهازك لتسجيل جلسة، وانقر على الزر إيقاف عند الرغبة في إكمال التسجيل. من المفترض أن يؤدي هذا الإجراء إلى حفظ ملف جديد باسم 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 من 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 المسجّل إلى جهازك والتحقق منه

بعد ذلك، ستشغّل جلسة الواقع المعزّز من ملف MP4.

4. تشغيل جلسة ARCore من ملف MP4

لديك الآن الزر تسجيل وبعض ملفات MP4 التي تحتوي على جلسات مسجَّلة. يمكنك الآن إعادة تشغيلها باستخدام واجهة برمجة التطبيقات ARCore Playback API.

إضافة واجهة مستخدم لزر التشغيل

قبل تنفيذ التشغيل، أضِف زرًا على واجهة المستخدم حتى يتمكّن المستخدم من إعلام ARCore بموعد بدء الجلسة وإيقاف تشغيلها.

في لوحة المشروع، افتح ملف app/res/layout/activity_main.xml.

activity_main-xml-location-in-project

في activity_main.xml، أضِف الرمز أدناه قبل علامة الإغلاق لإنشاء زر التشغيل الجديد وضبط معالِج الحدث على طريقة تُسمّى onClickPlayback(). ويكون هذا الزر مشابهًا للزر Record (تسجيل) وسيتم عرضه على يسار الشاشة.

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

  // 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، يتم اختيار الملفات من خلال "أداة اختيار ملفات النظام" في قسم "نشاط آخر". ويتم ذلك من خلال "إطار عمل الوصول إلى مساحة التخزين" (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.

// 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:

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

تشغيل المحتوى من الجهاز المستهدَف

حان الوقت للاطّلاع على ما أنجزته حتى الآن. اربط جهازك الجوّال بجهاز التطوير وانقر على تشغيل في "استوديو Android".

عند تشغيل التطبيق، من المفترض أن تظهر لك شاشة بها زر تسجيل أحمر على اليمين وزر تشغيل أخضر على اليسار.

playback-button.png

انقر على زر تشغيل واختَر أحد ملفات MP4 التي سجّلتها للتو. إذا لم تظهر لك أي أسماء ملفات تبدأ بـ arcore-، قد يكون جهازك لا يعرض المجلد Movies (الأفلام). في هذه الحالة، انتقِل إلى طراز الهاتف > "الأفلام" باستخدام القائمة في أعلى يمين الشاشة. قد تحتاج أيضًا إلى تفعيل خيار عرض وحدة التخزين الداخلية للكشف عن مجلد طراز الهاتف.

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 من مواضع عناصر الواقع المعزّز، ثم إنشاء عناصر الواقع المعزّز أثناء التشغيل في الموقع نفسه.

ضبط المسار الجديد للتسجيل

يجب تحديد مقطع صوتي جديد باستخدام معرّف فريد عالمي (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 ...
  }

حفظ وضع الارتساء أثناء التسجيل

في كل مرة ينقر المستخدم على طائرة تم رصدها، سيتم وضع علامة الواقع المعزّز على جهاز 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".

أولاً، انقر على الزر تسجيل لتسجيل جلسة. أثناء التسجيل، انقر على الطائرات التي تم رصدها لوضع بعض علامات الواقع المعزّز.

بعد توقّف التسجيل، انقر على زر التشغيل واختَر الملف الذي سجّلته للتو. من المفترض أن يبدأ التشغيل. لاحِظ كيف تظهر مواضع محدّدات الواقع المعزّز السابقة تمامًا كما كنت تنقر على التطبيق.

هذا كل ما عليك فعله في هذا الدرس التطبيقي حول الترميز.

6- تهانينا

تهانينا، لقد وصلت إلى نهاية هذا الدرس التطبيقي حول الترميز. لنلقِ نظرة على ما أنجزته في هذا الدرس التطبيقي حول الترميز:

  • تم إنشاء نموذج Hello AR Java في ARCore وتشغيله.
  • إضافة زر "تسجيل" إلى التطبيق لحفظ جلسة الواقع المعزّز في ملف MP4
  • إضافة زر تشغيل إلى التطبيق لتشغيل جلسة الواقع المعزّز من ملف MP4
  • تمت إضافة ميزة جديدة لحفظ الإعلانات الثابتة التي أنشأها المستخدم بتنسيق MP4 لإعادة تشغيلها.

هل استمتعت في هذا الدرس التطبيقي حول الترميز؟

نعم لا

هل استفدت من هذا الدرس التطبيقي حول الترميز؟

نعم لا

هل أكملت إنشاء التطبيق في هذا الدرس التطبيقي حول الترميز؟

نعم لا