ইন্টিগ্রেট অ্যাসেট ডেলিভারি (কোটলিন এবং জাভা)

আপনার জাভা কোড থেকে আপনার অ্যাপের অ্যাসেট প্যাকগুলি অ্যাক্সেস করতে এই গাইডের ধাপগুলি ব্যবহার করুন৷

কোটলিন এবং জাভার জন্য তৈরি করুন

আপনার প্রোজেক্টের অ্যান্ড্রয়েড অ্যাপ বান্ডেলে প্লে অ্যাসেট ডেলিভারি তৈরি করতে নিম্নলিখিত ধাপগুলি ব্যবহার করুন। এই পদক্ষেপগুলি সম্পাদন করার জন্য আপনাকে Android স্টুডিও ব্যবহার করতে হবে না।

  1. আপনার প্রজেক্টের build.gradle ফাইলে Android Gradle প্লাগইনের সংস্করণটি 4.0.0 বা তার পরবর্তী সংস্করণে আপডেট করুন।

  2. আপনার প্রকল্পের শীর্ষ-স্তরের ডিরেক্টরিতে, সম্পদ প্যাকের জন্য একটি ডিরেক্টরি তৈরি করুন। এই ডিরেক্টরির নামটি সম্পদ প্যাক নাম হিসাবে ব্যবহৃত হয়। অ্যাসেট প্যাকের নাম অবশ্যই একটি অক্ষর দিয়ে শুরু করতে হবে এবং এতে শুধুমাত্র অক্ষর, সংখ্যা এবং আন্ডারস্কোর থাকতে পারে।

  3. সম্পদ প্যাক ডিরেক্টরিতে, একটি build.gradle ফাইল তৈরি করুন এবং নিম্নলিখিত কোড যোগ করুন। নিশ্চিত করুন যে সম্পদ প্যাকের নাম এবং শুধুমাত্র একটি ডেলিভারি প্রকার উল্লেখ করুন:

    গ্রোভি

    // In the asset pack's build.gradle file:
    plugins {
      id 'com.android.asset-pack'
    }
    
    assetPack {
        packName = "asset-pack-name" // Directory name for the asset pack
        dynamicDelivery {
            deliveryType = "[ install-time | fast-follow | on-demand ]"
        }
    }
    

    কোটলিন

    // In the asset pack's build.gradle.kts file:
    plugins {
      id("com.android.asset-pack")
    }
    
    assetPack {
      packName.set("asset-pack-name") // Directory name for the asset pack
      dynamicDelivery {
        deliveryType.set("[ install-time | fast-follow | on-demand ]")
      }
    }
    
  4. প্রজেক্টের অ্যাপ build.gradle ফাইলে, আপনার প্রোজেক্টের প্রতিটি অ্যাসেট প্যাকের নাম নিচে দেখানো মত যোগ করুন:

    গ্রোভি

    // In the app build.gradle file:
    android {
        ...
        assetPacks = [":asset-pack-name", ":asset-pack2-name"]
    }
    

    কোটলিন

    // In the app build.gradle.kts file:
    android {
        ...
        assetPacks += listOf(":asset-pack-name", ":asset-pack2-name")
    }
    
  5. প্রজেক্টের settings.gradle ফাইলে, নিচের মত আপনার প্রোজেক্টের সমস্ত অ্যাসেট প্যাক অন্তর্ভুক্ত করুন:

    গ্রোভি

    // In the settings.gradle file:
    include ':app'
    include ':asset-pack-name'
    include ':asset-pack2-name'
    

    কোটলিন

    // In the settings.gradle.kts file:
    include(":app")
    include(":asset-pack-name")
    include(":asset-pack2-name")
    
  6. সম্পদ প্যাক ডিরেক্টরিতে, নিম্নলিখিত সাবডিরেক্টরি তৈরি করুন: src/main/assets

  7. src/main/assets ডিরেক্টরিতে সম্পদ রাখুন। আপনি এখানেও সাবডিরেক্টরি তৈরি করতে পারেন। আপনার অ্যাপ্লিকেশানের জন্য ডিরেক্টরি কাঠামোটি এখন নিম্নলিখিতগুলির মতো হওয়া উচিত:

    • build.gradle
    • settings.gradle
    • app/
    • asset-pack-name /build.gradle
    • asset-pack-name /src/main/assets/ your-asset-directories
  8. Gradle এর সাথে Android অ্যাপ বান্ডেল তৈরি করুন । জেনারেট করা অ্যাপ বান্ডেলে, রুট-লেভেল ডিরেক্টরিতে এখন নিম্নলিখিতগুলি অন্তর্ভুক্ত রয়েছে:

    • asset-pack-name /manifest/AndroidManifest.xml : সম্পদ প্যাকের শনাক্তকারী এবং বিতরণ মোড কনফিগার করে
    • asset-pack-name /assets/ your-asset-directories : যে ডিরেক্টরিতে সম্পদ প্যাকের অংশ হিসাবে বিতরণ করা সমস্ত সম্পদ রয়েছে

    Gradle প্রতিটি সম্পদ প্যাকের জন্য ম্যানিফেস্ট তৈরি করে এবং আপনার জন্য assets/ ডিরেক্টরি আউটপুট করে।

  9. (ঐচ্ছিক) যদি আপনি দ্রুত-অনুসরণ এবং অন-ডিমান্ড ডেলিভারি ব্যবহার করার পরিকল্পনা করেন তাহলে প্লে অ্যাসেট ডেলিভারি লাইব্রেরি অন্তর্ভুক্ত করুন

    গ্রোভি

    implementation "com.google.android.play:asset-delivery:2.2.2"
    // For Kotlin use asset-delivery-ktx
    implementation "com.google.android.play:asset-delivery-ktx:2.2.2"
    

    কোটলিন

    implementation("com.google.android.play:asset-delivery:2.2.2")
    // For Kotlin use core-ktx
    implementation("com.google.android.play:asset-delivery-ktx:2.2.2")
    

  10. (ঐচ্ছিক) বিভিন্ন টেক্সচার কম্প্রেশন ফর্ম্যাট সমর্থন করতে আপনার অ্যাপ বান্ডেল কনফিগার করুন।

Play Asset Delivery API-এর সাথে একীভূত করুন

প্লে অ্যাসেট ডেলিভারি জাভা এপিআই অ্যাসেট প্যাকের অনুরোধ, ডাউনলোড ম্যানেজ করা এবং অ্যাসেট অ্যাক্সেস করার জন্য AssetPackManager ক্লাস প্রদান করে। প্রথমে আপনার প্রজেক্টে প্লে অ্যাসেট ডেলিভারি লাইব্রেরি যোগ করা নিশ্চিত করুন।

আপনি যে অ্যাসেট প্যাক অ্যাক্সেস করতে চান তার ডেলিভারি টাইপ অনুসারে আপনি এই APIটি প্রয়োগ করুন। এই ধাপগুলি নিম্নলিখিত ফ্লোচার্টে দেখানো হয়েছে।

জাভা প্রোগ্রামিং ভাষার জন্য সম্পদ প্যাক প্রবাহ চিত্র

চিত্র 1. অ্যাসেট প্যাকগুলি অ্যাক্সেস করার জন্য ফ্লো ডায়াগ্রাম৷

ইন্সটল-টাইম ডেলিভারি

install-time হিসাবে কনফিগার করা সম্পদ প্যাকগুলি অ্যাপ লঞ্চের সময় অবিলম্বে উপলব্ধ। এই মোডে পরিবেশিত সম্পদগুলি অ্যাক্সেস করতে Java AssetManager API ব্যবহার করুন:

কোটলিন

import android.content.res.AssetManager
...
val context: Context = createPackageContext("com.example.app", 0)
val assetManager: AssetManager = context.assets
val stream: InputStream = assetManager.open("asset-name")

জাভা

import android.content.res.AssetManager;
...
Context context = createPackageContext("com.example.app", 0);
AssetManager assetManager = context.getAssets();
InputStream is = assetManager.open("asset-name");

দ্রুত-অনুসরণ এবং অন-ডিমান্ড ডেলিভারি

নিম্নলিখিত বিভাগগুলি ডাউনলোড করার আগে কীভাবে অ্যাসেট প্যাকগুলি সম্পর্কে তথ্য পেতে হয়, ডাউনলোড শুরু করতে API-কে কীভাবে কল করতে হয় এবং তারপরে ডাউনলোড করা প্যাকগুলি কীভাবে অ্যাক্সেস করতে হয় তা দেখায়৷ এই বিভাগগুলি fast-follow এবং on-demand সম্পদ প্যাকের ক্ষেত্রে প্রযোজ্য।

অবস্থা পরীক্ষা

প্রতিটি অ্যাসেট প্যাক অ্যাপের অভ্যন্তরীণ স্টোরেজে একটি আলাদা ফোল্ডারে সংরক্ষণ করা হয়। একটি সম্পদ প্যাকের রুট ফোল্ডার নির্ধারণ করতে getPackLocation() পদ্ধতি ব্যবহার করুন। এই পদ্ধতি নিম্নলিখিত মান প্রদান করে:

ফেরত মূল্য স্ট্যাটাস
একটি বৈধ AssetPackLocation অবজেক্ট সম্পদ প্যাক রুট ফোল্ডার assetsPath() এ অবিলম্বে অ্যাক্সেসের জন্য প্রস্তুত
null অজানা সম্পদ প্যাক বা সম্পদ উপলব্ধ নেই

সম্পদ প্যাক সম্পর্কে ডাউনলোড তথ্য পান

অ্যাসেট প্যাক আনার আগে অ্যাপগুলিকে ডাউনলোডের আকার প্রকাশ করতে হবে। ডাউনলোডের আকার এবং প্যাকটি ইতিমধ্যে ডাউনলোড হচ্ছে কিনা তা নির্ধারণ করতে requestPackStates() বা getPackStates() পদ্ধতি ব্যবহার করুন।

কোটলিন

suspend fun requestPackStates(packNames: List<String>): AssetPackStates

জাভা

Task<AssetPackStates> getPackStates(List<String> packNames)

requestPackStates() হল একটি সাসপেন্ড ফাংশন যা একটি AssetPackStates অবজেক্ট ফেরত দেয় যখন getPackStates() হল একটি অ্যাসিঙ্ক্রোনাস পদ্ধতি যা একটি Task<AssetPackStates> প্রদান করে। একটি AssetPackStates অবজেক্টের packStates() পদ্ধতি একটি Map<String, AssetPackState> প্রদান করে। এই মানচিত্রে প্রতিটি অনুরোধ করা সম্পদ প্যাকের অবস্থা রয়েছে, এটির নামের দ্বারা কী করা হয়েছে:

কোটলিন

AssetPackStates#packStates(): Map<String, AssetPackState>

জাভা

Map<String, AssetPackState> AssetPackStates#packStates()

চূড়ান্ত অনুরোধ নিম্নলিখিত দ্বারা দেখানো হয়:

কোটলিন

const val assetPackName = "assetPackName"
coroutineScope.launch {
  try {
    val assetPackStates: AssetPackStates =
      manager.requestPackStates(listOf(assetPackName))
    val assetPackState: AssetPackState =
      assetPackStates.packStates()[assetPackName]
  } catch (e: RuntimeExecutionException) {
    Log.d("MainActivity", e.message)
  }
}

জাভা

final String assetPackName = "myasset";

assetPackManager
    .getPackStates(Collections.singletonList(assetPackName))
    .addOnCompleteListener(new OnCompleteListener<AssetPackStates>() {
        @Override
        public void onComplete(Task<AssetPackStates> task) {
            AssetPackStates assetPackStates;
            try {
                assetPackStates = task.getResult();
                AssetPackState assetPackState =
                    assetPackStates.packStates().get(assetPackName);
            } catch (RuntimeExecutionException e) {
                Log.d("MainActivity", e.getMessage());
                return;
            })

নিম্নলিখিত AssetPackState পদ্ধতিগুলি অ্যাসেট প্যাকের আকার, এখন পর্যন্ত ডাউনলোড করা পরিমাণ (যদি অনুরোধ করা হয়), এবং ইতিমধ্যে অ্যাপে স্থানান্তরিত পরিমাণ প্রদান করে:

একটি সম্পদ প্যাকের স্থিতি পেতে, status() পদ্ধতিটি ব্যবহার করুন, যা একটি পূর্ণসংখ্যা হিসাবে স্ট্যাটাস প্রদান করে যা AssetPackStatus ক্লাসের একটি ধ্রুবক ক্ষেত্রের সাথে সম্পর্কিত। একটি সম্পদ প্যাক যা এখনও ইনস্টল করা হয়নি তার স্থিতি AssetPackStatus.NOT_INSTALLED আছে।

যদি একটি অনুরোধ ব্যর্থ হয়, errorCode() পদ্ধতি ব্যবহার করুন, যার রিটার্ন মান AssetPackErrorCode ক্লাসের একটি ধ্রুবক ক্ষেত্রের সাথে মিলে যায়।

ইনস্টল করুন

requestFetch() বা fetch() পদ্ধতি ব্যবহার করুন প্রথমবারের মতো একটি অ্যাসেট প্যাক ডাউনলোড করতে বা অ্যাসেট প্যাকের আপডেট সম্পূর্ণ করার জন্য কল করুন:

কোটলিন

suspend fun AssetPackManager.requestFetch(packs: List<String>): AssetPackStates

জাভা

Task<AssetPackStates> fetch(List<String> packNames)

এই পদ্ধতিটি একটি AssetPackStates অবজেক্ট প্রদান করে যার মধ্যে প্যাকগুলির একটি তালিকা এবং তাদের প্রাথমিক ডাউনলোডের অবস্থা এবং আকার রয়েছে। যদি requestFetch() বা fetch() এর মাধ্যমে অনুরোধ করা কোনো অ্যাসেট প্যাক ইতিমধ্যেই ডাউনলোড করা হয়, তাহলে ডাউনলোডের স্ট্যাটাস ফেরত দেওয়া হয় এবং কোনও অতিরিক্ত ডাউনলোড শুরু হয় না।

ডাউনলোড স্টেট মনিটর

সম্পদ প্যাকগুলির ইনস্টলেশন অগ্রগতি ট্র্যাক করতে আপনার একটি AssetPackStateUpdatedListener প্রয়োগ করা উচিত। স্বতন্ত্র সম্পদ প্যাকের স্থিতি ট্র্যাকিং সমর্থন করার জন্য স্ট্যাটাস আপডেটগুলি প্যাক প্রতি ভেঙে দেওয়া হয়। আপনার অনুরোধের জন্য অন্যান্য সমস্ত ডাউনলোড সম্পূর্ণ হওয়ার আগে আপনি উপলব্ধ সম্পদ প্যাকগুলি ব্যবহার করা শুরু করতে পারেন৷

কোটলিন

fun registerListener(listener: AssetPackStateUpdatedListener)
fun unregisterListener(listener: AssetPackStateUpdatedListener)

জাভা

void registerListener(AssetPackStateUpdatedListener listener)
void unregisterListener(AssetPackStateUpdatedListener listener)

বড় ডাউনলোড

যদি ডাউনলোডটি 200 MB-এর চেয়ে বড় হয় এবং ব্যবহারকারী Wi-Fi-এ না থাকে, তাহলে ডাউনলোড শুরু হবে না যতক্ষণ না ব্যবহারকারী স্পষ্টভাবে একটি মোবাইল ডেটা সংযোগ ব্যবহার করে ডাউনলোডের সাথে এগিয়ে যাওয়ার জন্য তাদের সম্মতি দেন৷ একইভাবে, যদি ডাউনলোডটি বড় হয় এবং ব্যবহারকারী Wi-Fi হারান, ডাউনলোডটি বিরতি দেওয়া হয় এবং একটি মোবাইল ডেটা সংযোগ ব্যবহার করে এগিয়ে যাওয়ার জন্য স্পষ্ট সম্মতি প্রয়োজন৷ একটি পজ করা প্যাকে WAITING_FOR_WIFI স্টেট আছে। ব্যবহারকারীকে সম্মতির জন্য অনুরোধ জানানোর জন্য UI ফ্লো ট্রিগার করতে, showConfirmationDialog() পদ্ধতি ব্যবহার করুন।

মনে রাখবেন যে অ্যাপটি এই পদ্ধতিতে কল না করলে, ডাউনলোডটি বিরতি দেওয়া হয় এবং ব্যবহারকারী যখন Wi-Fi সংযোগে ফিরে আসে শুধুমাত্র তখনই স্বয়ংক্রিয়ভাবে পুনরায় শুরু হবে।

প্রয়োজনীয় ব্যবহারকারী নিশ্চিতকরণ

যদি একটি প্যাকের REQUIRES_USER_CONFIRMATION স্থিতি থাকে, তবে ব্যবহারকারী showConfirmationDialog() এর সাথে দেখানো ডায়ালগটি গ্রহণ না করা পর্যন্ত ডাউনলোডটি এগিয়ে যাবে না। এই স্থিতি ঘটতে পারে যখন অ্যাপটি প্লে দ্বারা স্বীকৃত না হয়—উদাহরণস্বরূপ, যদি অ্যাপটি সাইড-লোড হয়। মনে রাখবেন যে এই ক্ষেত্রে showConfirmationDialog() কল করলে অ্যাপটি আপডেট হবে। আপডেটের পরে, আপনাকে আবার সম্পদের জন্য অনুরোধ করতে হবে।

নিম্নলিখিত একটি শ্রোতা বাস্তবায়ন একটি উদাহরণ:

কোটলিন

private val activityResultLauncher = registerForActivityResult(
    ActivityResultContracts.StartIntentSenderForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        Log.d(TAG, "Confirmation dialog has been accepted.")
    } else if (result.resultCode == RESULT_CANCELED) {
        Log.d(TAG, "Confirmation dialog has been denied by the user.")
    }
}

assetPackManager.registerListener { assetPackState ->
  when(assetPackState.status()) {
    AssetPackStatus.PENDING -> {
      Log.i(TAG, "Pending")
    }
    AssetPackStatus.DOWNLOADING -> {
      val downloaded = assetPackState.bytesDownloaded()
      val totalSize = assetPackState.totalBytesToDownload()
      val percent = 100.0 * downloaded / totalSize

      Log.i(TAG, "PercentDone=" + String.format("%.2f", percent))
    }
    AssetPackStatus.TRANSFERRING -> {
      // 100% downloaded and assets are being transferred.
      // Notify user to wait until transfer is complete.
    }
    AssetPackStatus.COMPLETED -> {
      // Asset pack is ready to use. Start the game.
    }
    AssetPackStatus.FAILED -> {
      // Request failed. Notify user.
      Log.e(TAG, assetPackState.errorCode())
    }
    AssetPackStatus.CANCELED -> {
      // Request canceled. Notify user.
    }
    AssetPackStatus.WAITING_FOR_WIFI,
    AssetPackStatus.REQUIRES_USER_CONFIRMATION -> {
      if (!confirmationDialogShown) {
        assetPackManager.showConfirmationDialog(activityResultLauncher);
        confirmationDialogShown = true
      }
    }
    AssetPackStatus.NOT_INSTALLED -> {
      // Asset pack is not downloaded yet.
    }
    AssetPackStatus.UNKNOWN -> {
      Log.wtf(TAG, "Asset pack status unknown")
    }
  }
}

জাভা

assetPackStateUpdateListener = new AssetPackStateUpdateListener() {
    private final ActivityResultLauncher<IntentSenderRequest> activityResultLauncher =
      registerForActivityResult(
          new ActivityResultContracts.StartIntentSenderForResult(),
          new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
              if (result.getResultCode() == RESULT_OK) {
                Log.d(TAG, "Confirmation dialog has been accepted.");
              } else if (result.getResultCode() == RESULT_CANCELED) {
                Log.d(TAG, "Confirmation dialog has been denied by the user.");
              }
            }
          });

    @Override
    public void onStateUpdate(AssetPackState assetPackState) {
      switch (assetPackState.status()) {
        case AssetPackStatus.PENDING:
          Log.i(TAG, "Pending");
          break;

        case AssetPackStatus.DOWNLOADING:
          long downloaded = assetPackState.bytesDownloaded();
          long totalSize = assetPackState.totalBytesToDownload();
          double percent = 100.0 * downloaded / totalSize;

          Log.i(TAG, "PercentDone=" + String.format("%.2f", percent));
          break;

        case AssetPackStatus.TRANSFERRING:
          // 100% downloaded and assets are being transferred.
          // Notify user to wait until transfer is complete.
          break;

        case AssetPackStatus.COMPLETED:
          // Asset pack is ready to use. Start the game.
          break;

        case AssetPackStatus.FAILED:
          // Request failed. Notify user.
          Log.e(TAG, assetPackState.errorCode());
          break;

        case AssetPackStatus.CANCELED:
          // Request canceled. Notify user.
          break;

        case AssetPackStatus.WAITING_FOR_WIFI:
        case AssetPackStatus.REQUIRES_USER_CONFIRMATION:
          if (!confirmationDialogShown) {
            assetPackManager.showConfirmationDialog(activityResultLauncher);
            confirmationDialogShown = true;
          }
          break;

        case AssetPackStatus.NOT_INSTALLED:
          // Asset pack is not downloaded yet.
          break;
        case AssetPackStatus.UNKNOWN:
          Log.wtf(TAG, "Asset pack status unknown")
          break;
      }
    }
}

বিকল্পভাবে, আপনি বর্তমান ডাউনলোডের স্থিতি পেতে getPackStates() পদ্ধতি ব্যবহার করতে পারেন। AssetPackStates ডাউনলোডের অগ্রগতি, ডাউনলোডের স্থিতি এবং যেকোন ব্যর্থতার ত্রুটি কোড ধারণ করে।

অ্যাসেট প্যাকগুলি অ্যাক্সেস করুন

ডাউনলোডের অনুরোধ COMPLETED অবস্থায় পৌঁছানোর পরে আপনি ফাইল সিস্টেম কল ব্যবহার করে একটি সম্পদ প্যাক অ্যাক্সেস করতে পারেন। সম্পদ প্যাকের রুট ফোল্ডার পেতে getPackLocation() পদ্ধতি ব্যবহার করুন।

সম্পদগুলি সম্পদ প্যাক রুট ডিরেক্টরির মধ্যে assets ডিরেক্টরিতে সংরক্ষণ করা হয়। আপনি সুবিধার পদ্ধতি assetsPath() ব্যবহার করে assets ডিরেক্টরির পথ পেতে পারেন। একটি নির্দিষ্ট সম্পদের পথ পেতে নিম্নলিখিত পদ্ধতি ব্যবহার করুন:

কোটলিন

private fun getAbsoluteAssetPath(assetPack: String, relativeAssetPath: String): String? {
    val assetPackPath: AssetPackLocation =
      assetPackManager.getPackLocation(assetPack)
      // asset pack is not ready
      ?: return null

    val assetsFolderPath = assetPackPath.assetsPath()
    // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets")
    return FilenameUtils.concat(assetsFolderPath, relativeAssetPath)
}

জাভা

private String getAbsoluteAssetPath(String assetPack, String relativeAssetPath) {
    AssetPackLocation assetPackPath = assetPackManager.getPackLocation(assetPack);

    if (assetPackPath == null) {
        // asset pack is not ready
        return null;
    }

    String assetsFolderPath = assetPackPath.assetsPath();
    // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets");
    String assetPath = FilenameUtils.concat(assetsFolderPath, relativeAssetPath);
    return assetPath;
}

অন্যান্য Play সম্পদ বিতরণ API পদ্ধতি

নিম্নলিখিত কিছু অতিরিক্ত API পদ্ধতি রয়েছে যা আপনি আপনার অ্যাপে ব্যবহার করতে চাইতে পারেন।

অনুরোধ বাতিল

একটি সক্রিয় সম্পদ প্যাক অনুরোধ বাতিল করতে cancel() ব্যবহার করুন। মনে রাখবেন যে এই অনুরোধটি একটি সর্বোত্তম-প্রচেষ্টা অপারেশন।

একটি সম্পদ প্যাক সরান

একটি সম্পদ প্যাক অপসারণের সময়সূচী করতে requestRemovePack() বা removePack() ব্যবহার করুন।

একাধিক সম্পদ প্যাকের অবস্থান পান

বাল্কে একাধিক অ্যাসেট প্যাকের স্ট্যাটাস জিজ্ঞাসা করতে getPackLocations() ব্যবহার করুন, যা অ্যাসেট প্যাক এবং তাদের অবস্থানগুলির একটি মানচিত্র প্রদান করে৷ getPackLocations() দ্বারা প্রত্যাবর্তিত মানচিত্রে বর্তমানে ডাউনলোড করা এবং আপ-টু-ডেট প্রতিটি প্যাকের জন্য একটি এন্ট্রি রয়েছে।

পরবর্তী পর্ব

স্থানীয়ভাবে এবং Google Play থেকে প্লে অ্যাসেট ডেলিভারি পরীক্ষা করুন