XR-এর জন্য Jetpack Compose-এর মাধ্যমে স্পেশিয়াল UI ডেভেলপ করা

প্রযোজ্য XR ডিভাইস
এইসব XR ডিভাইসের জন্য অভিজ্ঞতা তৈরি করতে এই নির্দেশিকা আপনাকে সাহায্য করে।
XR হেডসেট
ওয়্যারড XR গ্লাস

XR-এর জন্য Jetpack Compose-এর সাহায্যে, আপনি রো ও কলামের মতো পরিচিত Compose কনসেপ্ট ব্যবহার করে আপনার স্পেশিয়াল UI ও লেআউট ডিক্লেয়ারেটিভভাবে তৈরি করতে পারবেন। এর ফলে আপনি আগে থেকে থাকা Android UI-কে 3D স্পেসে এক্সটেন্ড করতে বা সম্পূর্ণ নতুন ইমারসিভ 3D অ্যাপ্লিকেশন তৈরি করতে পারবেন।

আপনি যদি আগে থেকেই থাকা Android Views-ভিত্তিক অ্যাপ স্পেশিয়ালাইজ করেন, তাহলে আপনার কাছে বেশ কিছু ডেভেলপমেন্ট বিকল্প আছে। আপনি ইন্টারঅপারেবিলিটি API ব্যবহার করতে পারেন, Compose ও Views একসাথে ব্যবহার করতে পারেন অথবা SceneCore লাইব্রেরির সাথে সরাসরি কাজ করতে পারেন। আরও বিবরণের জন্য ভিউ নিয়ে কাজ করার নির্দেশিকা দেখুন।

সাবস্পেস ও স্পেশিয়ালাইজড কম্পোনেন্ট সম্পর্কে

Android XR-এর জন্য অ্যাপ লেখার সময়, সাবস্পেস ও স্পেশিয়ালাইজড কম্পোনেন্ট-এর ধারণা বোঝা গুরুত্বপূর্ণ।

সাবস্পেস সম্পর্কে

Android XR-এর জন্য ডেভেলপ করার সময়, আপনাকে অ্যাপ বা লেআউটে একটি সাবস্পেস যোগ করতে হবে। সাবস্পেস হল আপনার অ্যাপের মধ্যে 3D স্পেসের একটি অংশ যেখানে আপনি 3D কন্টেন্ট প্লেস করতে, 3D লেআউট তৈরি করতে এবং অন্যথায় 2D কন্টেন্টে গভীরতা যোগ করতে পারবেন। স্পেশিয়ালাইজেশন চালু করা থাকলে তবেই সাবস্পেস রেন্ডার করা হয়। হোম স্পেস বা XR নয় এমন ডিভাইসে, সেই সাবস্পেসের মধ্যে থাকা যেকোনও কোড উপেক্ষা করা হয়।

সাবস্পেস তৈরি করার কয়েকটি উপায় আছে:

  • Subspace: এই কম্পোজ করার উপযুক্ত আইটেমটি একটি নতুন, স্বতন্ত্র স্পেশিয়াল UI হায়ারার্কি তৈরি করে। এটি কোনও প্যারেন্ট Subspace-এর স্পেশিয়াল পজিশন, ওরিয়েন্টেশন বা স্কেল ইনহেরিট করে না যার মধ্যে এটি নেস্ট করা আছে। সিস্টেমের সাজেস্ট করা কন্টেন্ট বক্সের সাথে Subspace অটোমেটিক বাউন্ড হয়ে যায়।
  • PlanarEmbeddedSubspace: এই কম্পোজ করার উপযুক্ত আইটেমটি আপনার অ্যাপের UI হায়ারার্কির মধ্যে প্লেস করা যেতে পারে, এর ফলে আপনি 2D ও স্পেশিয়াল UI-এর লেআউট বজায় রাখতে পারবেন। PlanarEmbeddedSubspace সেটির পেরেন্টের সীমাবদ্ধতা ও পজিশনিংকে সম্মান করে। এর মধ্যে প্লেস করা 3D কন্টেন্টকে তারপর এই 2D-নির্ধারিত জায়গার তুলনায় পজিশন করা হয়।

আরও তথ্যের জন্য, আপনার অ্যাপে সাবস্পেস যোগ করুন দেখুন।

স্পেশিয়ালাইজড কম্পোনেন্ট সম্পর্কে

সাবস্পেস কম্পোজেবল: এই কম্পোনেন্টগুলি শুধুমাত্র সাবস্পেসে রেন্ডার করা যেতে পারে। 2D লেআউটের মধ্যে প্লেস করার আগে সেগুলিকে অবশ্যই Subspace-এর মধ্যে রাখতে হবে। SubspaceModifier আপনাকে নিজের সাবস্পেস কম্পোজেবলগুলিতে ডেপথ, অফসেট ও পজিশনিং-এর মতো অ্যাট্রিবিউট যোগ করতে দেয়।

অন্যান্য স্পেশিয়ালাইজড কম্পোনেন্টকে সাবস্পেসের মধ্যে কল করার প্রয়োজন নেই। এগুলিতে স্পেশিয়াল কন্টেনারের মধ্যে মোড়ানো কনভেনশনাল 2D এলিমেন্ট থাকে। এইসব এলিমেন্ট, যদি দুটির জন্যই সংজ্ঞায়িত করা থাকে, তাহলে 2D বা 3D লেআউটের মধ্যে ব্যবহার করা যেতে পারে। স্পেশিয়ালাইজেশন চালু করা না থাকলে, তাদের স্পেশিয়ালাইজড ফিচার উপেক্ষা করা হবে এবং সেগুলি 2D কাউন্টারপার্টে ফিরে যাবে।

স্পেশিয়াল প্যানেল তৈরি করা

SpatialPanel হল একটি কম্পোজ করা যায় এমন সাবস্পেস যা আপনাকে অ্যাপ কন্টেন্ট দেখাতে দেয়–যেমন, আপনি স্পেশিয়াল প্যানেলে ভিডিও প্লেব্যাক, স্থির ছবি বা অন্য যেকোনও কন্টেন্ট দেখাতে পারেন।

স্পেশিয়াল UI প্যানেলের উদাহরণ

আপনি SubspaceModifier ব্যবহার করে স্পেশিয়াল প্যানেলের সাইজ, আচরণ ও পজিশন পরিবর্তন করতে পারবেন। নিচে দেওয়া উদাহরণে তা দেখানো হয়েছে।

Subspace {
    SpatialPanel(
        SubspaceModifier
            .height(824.dp)
            .width(1400.dp)
            .movable()
            .resizable(),
    ) {
        SpatialPanelContent()
    }
}

@Composable
fun SpatialPanelContent() {
    Box(
        Modifier
            .background(color = Color.Black)
            .height(500.dp)
            .width(500.dp),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = "Spatial Panel",
            color = Color.White,
            fontSize = 25.sp
        )
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ পয়েন্ট

  • SpatialPanel API হল সাবস্পেস কম্পোজ করার উপযুক্ত, তাই আপনাকে অবশ্যই Subspace-এর মধ্যে সেগুলি কল করতে হবে। সাবস্পেসের বাইরে তাদের কল করলে ব্যতিক্রম ঘটে।
  • SpatialPanel-এর সাইজ SubspaceModifier-এ দেওয়া height ও width স্পেসিফিকেশন ব্যবহার করে সেট করা হয়েছে। এইসব স্পেসিফিকেশন বাদ দিলে, প্যানেলের কন্টেন্টের পরিমাপের মাধ্যমে প্যানেলের সাইজ নির্ধারণ করা হয়।
  • movable সাবস্পেস মডিফায়ার যোগ করে ব্যবহারকারীকে প্যানেল সরানোর অনুমতি দিন।
  • resizable সাবস্পেস মডিফায়ার যোগ করে ব্যবহারকারীকে প্যানেলের সাইজ পরিবর্তন করার অনুমতি দিন।
  • সাইজ ও পজিশনিং সংক্রান্ত বিস্তারিত তথ্যের জন্য আমাদের স্পেশিয়াল প্যানেল ডিজাইন সংক্রান্ত নির্দেশিকা দেখুন। কোড প্রয়োগ করা সম্পর্কে আরও নির্দিষ্ট তথ্য পেতে, আমাদের রেফারেন্স ডকুমেন্টেশন দেখুন।

movable মডিফায়ার কীভাবে কাজ করে

ব্যবহারকারী কোনও প্যানেল নিজের থেকে দূরে সরিয়ে নিলে, ডিফল্ট হিসেবে, movable মডিফায়ার প্যানেলটিকে সেই একই উপায়ে স্কেল করে যেভাবে সিস্টেম হোম স্পেসে প্যানেলের সাইজ পরিবর্তন করে। বাচ্চাদের জন্য তৈরি করা সব কন্টেন্ট এই আচরণ মেনে চলে। এটি বন্ধ করতে, shouldScaleWithDistance প্যারামিটারকে false হিসেবে সেট করুন।

একটি অরবিটার তৈরি করা

অরবিটার হল একটি স্পেশিয়াল UI কম্পোনেন্ট। এটি SpatialColumn, SpatialRow বা SpatialBox-এর মতো সংশ্লিষ্ট স্পেশিয়াল প্যানেল বা স্পেশিয়াল লেআউট কম্পোনেন্টের সাথে অ্যাটাচ করার জন্য ডিজাইন করা হয়েছে। কোনও অরবিটারে সাধারণত নেভিগেশন এবং যে এন্টিটির সাথে এটি অ্যাঙ্কর করা হয়েছে, সেই সম্পর্কিত প্রাসঙ্গিক অ্যাকশন আইটেম থাকে। যেমন, ভিডিও কন্টেন্ট দেখানোর জন্য আপনি স্পেশিয়াল প্যানেল তৈরি করে থাকলে, আপনি অরবিটারের মধ্যে ভিডিও প্লেব্যাক কন্ট্রোল যোগ করতে পারেন।

একটি অরবিটারের উদাহরণ

নিম্নলিখিত উদাহরণে দেখানো হয়েছে, নেভিগেশনের মতো ব্যবহারকারীর কন্ট্রোল র‍্যাপ করতে, 2D লেআউটের মধ্যে একটি SpatialPanel-এ একটি অরবিটার কল করুন। এটি করলে, আপনার 2D লেআউট থেকে সেগুলি এক্সট্র্যাক্ট করা হয় এবং আপনার কনফিগারেশন অনুযায়ী স্পেশিয়াল প্যানেলে অ্যাটাচ করা হয়।

Subspace {
    SpatialPanel(
        SubspaceModifier
            .height(824.dp)
            .width(1400.dp)
            .resizable()
            .movable(),
    ) {
        SpatialPanelContent()
        OrbiterExample()
    }
}

@Composable
fun OrbiterExample() {
    Orbiter(
        alignment = OrbiterAlignment.BottomCenter(
            edgeOffsetType = OrbiterEdgeOffsetType.OuterEdge,
            offset = DpVolumeOffset(y = 96.dp)
        ),
    ) {
        Surface(Modifier.clip(CircleShape)) {
            Row(
                Modifier
                    .background(color = Color.Black)
                    .height(100.dp)
                    .width(600.dp),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(
                    text = "Orbiter",
                    color = Color.White,
                    fontSize = 50.sp
                )
            }
        }
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ পয়েন্ট

  • অরবিটার হল স্পেশিয়াল UI কম্পোনেন্ট, তাই কোডটি 2D বা 3D লেআউটে পুনর্ব্যবহার করা যেতে পারে। 2D লেআউটে, আপনার অ্যাপ শুধুমাত্র অরবিটারের মধ্যে থাকা কন্টেন্ট রেন্ডার করে এবং অরবিটারটিকে এড়িয়ে যায়।
  • কীভাবে অরবিটার ব্যবহার ও ডিজাইন করতে হয় সেই সম্পর্কে আরও তথ্য পেতে, আমাদের ডিজাইন সংক্রান্ত নির্দেশিকা দেখুন।

স্পেশিয়াল লেআউটে একাধিক স্পেশিয়াল প্যানেল যোগ করা

আপনি একাধিক স্পেশিয়াল প্যানেল তৈরি করতে এবং SpatialRow, SpatialColumn, SpatialBox এবং SpatialSpacer ব্যবহার করে সেগুলিকে স্পেশিয়াল লেআউটের মধ্যে প্লেস করতে পারবেন।

স্পেশিয়াল লেআউটে একাধিক স্পেশিয়াল প্যানেলের উদাহরণ

নিচের কোডের উদাহরণ থেকে এটি কীভাবে করতে হয় তা জানতে পারবেন।

Subspace {
    SpatialRow {
        SpatialColumn {
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Top Left")
            }
            SpatialPanel(SubspaceModifier.height(200.dp).width(400.dp)) {
                SpatialPanelContent("Middle Left")
            }
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Bottom Left")
            }
        }
        SpatialColumn {
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Top Right")
            }
            SpatialPanel(SubspaceModifier.height(200.dp).width(400.dp)) {
                SpatialPanelContent("Middle Right")
            }
            SpatialPanel(SubspaceModifier.height(250.dp).width(400.dp)) {
                SpatialPanelContent("Bottom Right")
            }
        }
    }
}

@Composable
fun SpatialPanelContent(text: String) {
    Column(
        Modifier
            .background(color = Color.Black)
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "Panel",
            color = Color.White,
            fontSize = 15.sp
        )
        Text(
            text = text,
            color = Color.White,
            fontSize = 25.sp,
            fontWeight = FontWeight.Bold
        )
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ পয়েন্ট

  • SpatialRow, SpatialColumn, SpatialBox এবং SpatialSpacer হল সব সাবস্পেস কম্পোজ করার উপযুক্ত এবং এগুলিকে অবশ্যই সাবস্পেসের মধ্যে রাখতে হবে।
  • আপনার লেআউট কাস্টমাইজ করতে SubspaceModifier ব্যবহার করুন।
  • এক সারিতে একাধিক প্যানেল সহ লেআউটের জন্য, আমরা SubspaceModifier ব্যবহার করে ৮২৫dp-এর কার্ভ রেডিয়াস সেট করার সাজেশন দিই যাতে প্যানেলগুলি আপনার ব্যবহারকারীকে ঘিরে থাকে। বিস্তারিত জানতে আমাদের ডিজাইন সংক্রান্ত নির্দেশিকা দেখুন।

SpatialGltfModel ব্যবহার করে আপনার লেআউটে 3D অবজেক্ট যোগ করা

Android XR-এ 3D মডেলের জন্য glTF ফর্ম্যাট কাজ করে, সাধারণত এগুলি .glb ফাইল হিসেবে সেভ করা হয়। আপনার লেআউটে এইসব অবজেক্ট যোগ করতে, আপনাকে SpatialGltfModel কম্পোজ করার উপযুক্ত আইটেম ব্যবহার করতে হবে। এই API অ্যাসেট লোড করা এবং সেগুলির স্টেট ম্যানেজ করার প্রসেসকে সহজ করে তোলে।

কোনও মডেল দেখাতে, প্রথমে rememberSpatialGltfModelState ব্যবহার করে এর সোর্স ও স্টেট নির্ধারণ করুন। আপনি নিজের অ্যাপের assets ফোল্ডার, URI বা raw data থেকে মডেল লোড করতে পারবেন।

val modelState = rememberSpatialGltfModelState(
    source = SpatialGltfModelSource.fromPath(
        Paths.get("models/model_name.glb")
    )
)

স্টেট নির্দিষ্ট করা হয়ে গেলে, SpatialGltfModel কম্পোজ করার উপযুক্ত আইটেম ব্যবহার করে এটিকে সাবস্পেসের মধ্যে রেন্ডার করুন।

SpatialGltfModel(state = modelState, modifier = SubspaceModifier)

কোড সম্পর্কে গুরুত্বপূর্ণ পয়েন্ট

  • অ্যাসিঙ্ক্রোনাস লোডিং: মডেলটি অ্যাসিঙ্ক্রোনাস পদ্ধতিতে লোড করা হয়। প্রাথমিক কম্পোজিশনের সময়, এর ইন্ট্রিনসিক সাইজ শূন্য হতে পারে; মডেল রেডি হয়ে গেলে লেআউট আবার পরিমাপ করে।
  • কন্ট্রোলিং স্টেট: লোডিং স্ট্যাটাস কোয়েরি করতে বা অ্যানিমেশন কন্ট্রোল করতে SpatialGltfModelState.status ব্যবহার করুন।
  • সাইজিং ও স্কেলিং: ডিফল্ট হিসেবে, লেআউটের সাইজ অ্যাসেটের বাউন্ডিং বক্সের সাথে ম্যাচ করে। নির্দিষ্ট সীমার মধ্যে ফিট করার জন্য মডেলটিকে একইভাবে স্কেল করতে, আপনি SubspaceModifier.size ব্যবহার করে এটি ওভাররাইড করতে পারেন।

আপনার লেআউটে এন্টিটি প্লেস করতে SceneCoreEntity ব্যবহার করা

SceneCoreEntity কম্পোজ করার উপযুক্ত আইটেম Jetpack SceneCore এবং Compose for XR লাইব্রেরিকে কানেক্ট করে, যাতে আপনি Compose লেআউটে SceneCore-এর সাহায্যে তৈরি এনটিটি ব্যবহার করতে পারেন। এটি আপনাকে নিম্ন-স্তরের এন্টিটি ও কাস্টম কম্পোনেন্ট তৈরি করতে দেয়, পাশাপাশি Compose-কে সেইসব এন্টিটির সাইজ, পজিশন, রিপ্যারেন্ট, চাইল্ড যোগ করা ও মডিফায়ার প্রয়োগ করার অনুমতি দেয়।

val virtualPixelDensity = session.scene.virtualPixelDensity

Subspace {
    SceneCoreEntity(
        modifier = SubspaceModifier.offset(x = 50.dp),
        factory = {
            SurfaceEntity.create(
                session = session,
                pose = Pose.Identity,
                stereoMode = SurfaceEntity.StereoMode.MONO
            )
        },
        update = { entity ->
            // Compose state changes may be applied to the SceneCore entity here.
            entity.stereoMode = SurfaceEntity.StereoMode.SIDE_BY_SIDE
        },
        sizeAdapter =
            object : SceneCoreEntitySizeAdapter<SurfaceEntity> {
                override fun onLayoutSizeChanged(
                    entity: SurfaceEntity,
                    size: IntVolumeSize
                ) {
                    val extents = FloatSize2d(
                        size.width.toFloat(),
                        size.height.toFloat()
                    )
                    entity.shape = SurfaceEntity.Shape.Quad(extents)
                }
            },
    ) {
        // Content here will be children of the SceneCoreEntity
        // in the scene graph.
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ পয়েন্ট

  • ফ্যাক্টরি ব্লক: ফ্যাক্টরি ব্লক হল সেই জায়গা যেখানে আপনি অন্তর্নিহিত SceneCore এন্টিটি ইনিশিয়ালাইজ করেন।
  • আপডেট ব্লক: আপনার Compose স্টেটে হওয়া পরিবর্তনের প্রতিক্রিয়া হিসেবে এন্টিটির প্রপার্টি পরিবর্তন করতে আপডেট ব্লক ব্যবহার করুন।
  • সাইজ অ্যাডাপ্টেশন: sizeAdapter কম্পোজ লেআউট সিস্টেমে এন্টিটির ডাইমেনশন সম্পর্কে তথ্য দেয়।

অতিরিক্ত তথ্য

ছবি বা ভিডিও কন্টেন্টের জন্য সারফেস যোগ করা

SpatialExternalSurface হল একটি কম্পোজ করা যায় এমন সাবস্পেস যা তৈরি করে এবং ম্যানেজ করে Surface যার মধ্যে আপনার অ্যাপ কন্টেন্ট ড্র করতে পারে, যেমন কোনও ছবি বা ভিডিও। SpatialExternalSurface স্টেরিওস্কোপিক বা মনোস্কোপিক কন্টেন্ট দু'টির মধ্যে যেকোনও একটির সাথে কাজ করে।

এই উদাহরণে দেখানো হয়েছে যে কীভাবে Media3 Exoplayer ও SpatialExternalSurface ব্যবহার করে পাশাপাশি স্টিরিওস্কোপিক ভিডিও লোড করতে হয়:

@OptIn(ExperimentalComposeApi::class)
@Composable
fun SpatialExternalSurfaceContent() {
    val context = LocalContext.current
    Subspace {
        SpatialExternalSurface(
            modifier = SubspaceModifier
                .width(1200.dp) // Default width is 400.dp if no width modifier is specified
                .height(676.dp), // Default height is 400.dp if no height modifier is specified
            // Use StereoMode.Mono, StereoMode.SideBySide, or StereoMode.TopBottom, depending
            // upon which type of content you are rendering: monoscopic content, side-by-side stereo
            // content, or top-bottom stereo content
            stereoMode = StereoMode.SideBySide,
        ) {
            val exoPlayer = remember { ExoPlayer.Builder(context).build() }
            val videoUri = Uri.Builder()
                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
                // Represents a side-by-side stereo video, where each frame contains a pair of
                // video frames arranged side-by-side. The frame on the left represents the left
                // eye view, and the frame on the right represents the right eye view.
                .path("sbs_video.mp4")
                .build()
            val mediaItem = MediaItem.fromUri(videoUri)

            // onSurfaceCreated is invoked only one time, when the Surface is created
            onSurfaceCreated { surface ->
                exoPlayer.setVideoSurface(surface)
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.prepare()
                exoPlayer.play()
            }
            // onSurfaceDestroyed is invoked when the SpatialExternalSurface composable and its
            // associated Surface are destroyed
            onSurfaceDestroyed { exoPlayer.release() }
        }
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ পয়েন্ট

  • আপনি কোন ধরনের কন্টেন্ট রেন্ডার করছেন তার উপর নির্ভর করে StereoMode-এর মান Mono, SideBySide বা TopBottom হিসেবে সেট করুন:
    • Mono: ছবি বা ভিডিও ফ্রেমে একটিই অভিন্ন ছবি থাকে যা দুটি চোখেই দেখানো হয়।
    • SideBySide: ছবি বা ভিডিও ফ্রেমে পাশাপাশি সাজানো একজোড়া ছবি বা ভিডিও ফ্রেম থাকে, যেখানে বাঁদিকের ছবি বা ফ্রেম বাঁদিকের চোখের ভিউ এবং ডানদিকের ছবি বা ফ্রেম ডানদিকের চোখের ভিউ দেখায়।
    • TopBottom: ছবি বা ভিডিও ফ্রেমে দুটি ছবি বা ভিডিও ফ্রেম উল্লম্বভাবে সাজানো থাকে, যেখানে সবচেয়ে উপরে থাকা ছবি বা ফ্রেমটি বাম চোখের ভিউ এবং সবচেয়ে নিচে থাকা ছবি বা ফ্রেমটি ডান চোখের ভিউ দেখায়।
  • SpatialExternalSurface শুধুমাত্র আয়তাকার সারফেসের সাথে কাজ করে।
  • এই Surface ইনপুট ইভেন্ট ক্যাপচার করে না।
  • StereoMode পরিবর্তনগুলি অ্যাপ্লিকেশন রেন্ডারিং বা ভিডিও ডিকোডিংয়ের সাথে সিঙ্ক্রোনাইজ করা সম্ভব নয়।
  • এই কম্পোজ করার উপযুক্ত আইটেমটি অন্য প্যানেলের সামনে রেন্ডার করা যায় না, তাই লেআউটে অন্য প্যানেল থাকলে MovePolicy ব্যবহার করা উচিত নয়।

DRM সুরক্ষিত ভিডিও কন্টেন্টের জন্য সারফেস যোগ করা

SpatialExternalSurface DRM-সুরক্ষিত ভিডিও স্ট্রিম প্লেব্যাক করতেও পারে। এটি চালু করতে, আপনাকে অবশ্যই একটি সুরক্ষিত সারফেস তৈরি করতে হবে যা সুরক্ষিত গ্রাফিক্স বাফার রেন্ডার করে। এর ফলে কন্টেন্ট স্ক্রিন-রেকর্ড করা বা সুরক্ষিত নয় এমন সিস্টেম কম্পোনেন্ট দ্বারা অ্যাক্সেস করা যায় না।

নিরাপদ সারফেস তৈরি করতে, SpatialExternalSurface কম্পোজ করার উপযুক্ত আইটেমে SpatialExternalSurfaceProtection প্যারামিটারকে SpatialExternalSurfaceProtection.Protected হিসেবে সেট করুন। এছাড়াও, লাইসেন্স সার্ভার থেকে লাইসেন্স পাওয়ার বিষয়টি ম্যানেজ করতে, আপনাকে উপযুক্ত DRM তথ্য সহ Media3 Exoplayer কনফিগার করতে হবে।

DRM-সুরক্ষিত ভিডিও স্ট্রিম চালানোর জন্য কীভাবে SpatialExternalSurface এবং ExoPlayer কনফিগার করতে হয় তা নিম্নলিখিত উদাহরণ থেকে জানুন:

@OptIn(ExperimentalComposeApi::class)
@Composable
fun DrmSpatialVideoPlayer() {
    val context = LocalContext.current
    Subspace {
        SpatialExternalSurface(
            modifier = SubspaceModifier
                .width(1200.dp)
                .height(676.dp),
            stereoMode = StereoMode.SideBySide,
            surfaceProtection = SpatialExternalSurfaceProtection.Protected
        ) {
            val exoPlayer = remember { ExoPlayer.Builder(context).build() }

            // Define the URI for your DRM-protected content and license server.
            val videoUri = "https://your-content-provider.com/video.mpd"
            val drmLicenseUrl = "https://your-license-server.com/license"

            // Build a MediaItem with the necessary DRM configuration.
            val mediaItem = MediaItem.Builder()
                .setUri(videoUri)
                .setDrmConfiguration(
                    MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
                        .setLicenseUri(drmLicenseUrl)
                        .build()
                )
                .build()

            onSurfaceCreated { surface ->
                // The created surface is secure and can be used by the player.
                exoPlayer.setVideoSurface(surface)
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.prepare()
                exoPlayer.play()
            }

            onSurfaceDestroyed { exoPlayer.release() }
        }
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ পয়েন্ট

  • সুরক্ষিত সারফেস: সেটিং surfaceProtection = SpatialExternalSurfaceProtection.Protected SpatialExternalSurface চালু করা জরুরি যাতে অন্তর্নিহিত Surface DRM কন্টেন্টের জন্য উপযুক্ত সুরক্ষিত বাফার দ্বারা ব্যাক-আপ নেওয়া হয়।
  • DRM কনফিগারেশন: আপনাকে অবশ্যই DRM স্কিম (যেমন, C.WIDEVINE_UUID) এবং আপনার লাইসেন্স সার্ভারের URI সহ MediaItem কনফিগার করতে হবে। DRM সেশন ম্যানেজ করতে ExoPlayer এই তথ্য ব্যবহার করে।
  • সুরক্ষিত কন্টেন্ট: সুরক্ষিত সারফেসে রেন্ডার করার সময়, ভিডিও কন্টেন্ট ডিকোড করা হয় এবং সুরক্ষিত পাথে দেখানো হয়, যা কন্টেন্ট লাইসেন্সিং সংক্রান্ত প্রয়োজনীয়তা পূরণ করতে সাহায্য করে। এছাড়াও, এটি স্ক্রিন ক্যাপচারে কন্টেন্ট দেখানো আটকায়।

অন্যান্য স্পেশিয়াল UI কম্পোনেন্ট যোগ করা

আপনার অ্যাপ্লিকেশনের UI হায়ারার্কির যেকোনও জায়গায় স্পেশিয়াল UI কম্পোনেন্ট প্লেস করা যেতে পারে। আপনার 2D UI-তে এইসব এলিমেন্ট আবার ব্যবহার করা যাবে এবং স্পেশিয়াল ক্ষমতা চালু করা থাকলেই কেবলমাত্র এদের স্পেশিয়াল অ্যাট্রিবিউট দেখা যাবে। এর ফলে, আপনাকে দু'বার কোড না লিখেও মেনু, ডায়ালগ ও অন্যান্য কম্পোনেন্টে এলিভেশন যোগ করতে পারবেন। এইসব এলিমেন্ট কীভাবে ব্যবহার করতে হয় তা আরও ভালোভাবে বুঝতে স্পেশিয়াল UI-এর নিম্নলিখিত উদাহরণ দেখুন।

UI কম্পোনেন্ট

স্পেশিয়ালাইজেশন চালু করা থাকলে

2D এনভায়রনমেন্টে

SpatialDialog

উন্নত ডায়ালগ দেখানোর জন্য প্যানেলটি z-ডেপ্তে সামান্য পিছনের দিকে পুশ করা হবে

2D Dialog-তে ফিরে আসে।

SpatialPopup

উন্নত পপ-আপ দেখানোর জন্য প্যানেলটি z-ডেপ্তে সামান্য পিছনের দিকে পুশ করা হবে

2D Popup-তে ফিরে আসে।

SpatialElevation

SpatialElevationLevel-কে উচ্চতা যোগ করার জন্য সেট করা যেতে পারে।

স্পেসিয়াল এলিভেশন ছাড়া শো।

SpatialDialog

এটি একটি ডায়ালগের উদাহরণ যা কিছুক্ষণ পরে খোলে। SpatialDialog ব্যবহার করা হলে, ডায়ালগটি স্পেশিয়াল প্যানেলের মতো একই z-ডেপ্তে দেখা যায় এবং স্পেশিয়ালাইজেশন চালু করা থাকলে প্যানেলটি ১২৫dp পিছিয়ে যায়। স্পেশিয়ালাইজেশন চালু না থাকলেও SpatialDialog ব্যবহার করা যেতে পারে, সেক্ষেত্রে SpatialDialog-এর 2D সমতুল্য, Dialog ব্যবহার করা হয়।

@Composable
fun DelayedDialog() {
    var showDialog by remember { mutableStateOf(false) }
    LaunchedEffect(Unit) {
        delay(3000.milliseconds)
        showDialog = true
    }
    if (showDialog) {
        SpatialDialog(
            onDismissRequest = { showDialog = false },
            SpatialDialogProperties(
                dismissOnBackPress = true
            )
        ) {
            Box(
                Modifier
                    .height(150.dp)
                    .width(150.dp)
            ) {
                Button(onClick = { showDialog = false }) {
                    Text("OK")
                }
            }
        }
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ পয়েন্ট

কাস্টম প্যানেল ও লেআউট তৈরি করা

Compose for XR-এ কাজ করে না এমন কাস্টম প্যানেল তৈরি করতে, আপনি PanelEntity ইনস্ট্যান্স ও সিন গ্রাফের সাথে সরাসরি কাজ করতে পারেন। এর জন্য SceneCore API ব্যবহার করুন।

স্পেশিয়াল প্যানেল ও লেআউটে অরবিটার অ্যাঙ্কর করা

আপনি SpatialPanels এবং Compose-এ ঘোষণা করা স্পেশিয়াল লেআউট কম্পোনেন্টের সাথে অরবিটার অ্যাঙ্কর করতে পারবেন। এর মধ্যে রয়েছে SpatialRow, SpatialColumn বা SpatialBox-এর মতো UI এলিমেন্টের স্পেশিয়াল লেআউটে একটি অরবিটার ঘোষণা করা। আপনি যেখানে এটি ঘোষণা করেছেন, তার সবচেয়ে কাছে থাকা মূল রাউটারের সাথে অরবিটার অ্যাঙ্কর করে।

আপনি কোথায় অরবিটার ঘোষণা করছেন তার উপর নির্ভর করে অরবিটারের আচরণ নির্ধারিত হয়:

  • SpatialPanel-এ মোড়ানো 2D লেআউটে (যেমন, আগের কোড স্নিপেটে দেখানো হয়েছে ), অরবিটার সেই SpatialPanel-এর সাথে অ্যাঙ্কর করে।
  • Subspace-এ, অরবিটার সবচেয়ে কাছের পেরেন্ট এন্টিটির সাথে অ্যাঙ্কর করে, যা হল অরবিটার যে স্পেশিয়াল লেআউটে ঘোষণা করা হয়েছে।

নিম্নলিখিত উদাহরণে দেখানো হয়েছে কীভাবে কোনও স্পেশিয়াল সারিতে অরবিটার অ্যাঙ্কর করতে হয়:

Subspace {
    SpatialRow {
        Orbiter(
            alignment = OrbiterAlignment.TopCenter(
                edgeOffsetType = OrbiterEdgeOffsetType.OuterEdge,
                offset = DpVolumeOffset(y = 8.dp)
            ),
            shape = SpatialRoundedCornerShape(size = CornerSize(50))
        ) {
            Text(
                "Hello World!",
                style = MaterialTheme.typography.titleMedium,
                modifier = Modifier
                    .background(Color.White)
                    .padding(16.dp)
            )
        }
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
        ) {
            Box(
                modifier = Modifier
                    .background(Color.Red)
            )
        }
        SpatialPanel(
            SubspaceModifier
                .height(824.dp)
                .width(1400.dp)
        ) {
            Box(
                modifier = Modifier
                    .background(Color.Blue)
            )
        }
    }
}

কোড সম্পর্কে গুরুত্বপূর্ণ পয়েন্ট

  • আপনি 2D লেআউটের বাইরে কোনও অরবিটার ঘোষণা করলে, অরবিটারটি এর সবচেয়ে কাছের পেরেন্ট এন্টিটির সাথে অ্যাঙ্কর করে। এই ক্ষেত্রে, অরবিটারটি SpatialRow-এর মধ্যে ঘোষণা করা হয়েছে, সেটির উপরের দিকে অ্যাঙ্কর করে।
  • SpatialRow, SpatialColumn, SpatialBox-এর মতো স্পেশিয়াল লেআউটে কন্টেন্টহীন এন্টিটি যুক্ত থাকে। তাই, কোনও স্পেশিয়াল লেআউটে ঘোষিত অরবিটার সেই লেআউটের সাথে অ্যাঙ্কর করে।

এছাড়াও দেখুন