টিভিতে ব্যবহারকারীর ইন্টার‍্যাকশন ম্যানেজ করা

লাইভ টিভি দেখার সময়, ব্যবহারকারী চ্যানেল পরিবর্তন করেন এবং তথ্য অদৃশ্য হওয়ার আগে চ্যানেল ও প্রোগ্রাম সংক্রান্ত তথ্য সংক্ষেপে দেখানো হয়। অন্যান্য ধরনের তথ্য, যেমন মেসেজ ("বাড়িতে চেষ্টা করবেন না"), সাবটাইটেল বা বিজ্ঞাপন হয়ত থেকে যেতে পারে। যেকোনও TV অ্যাপের মতো, এই ধরনের তথ্য স্ক্রিনে চলা প্রোগ্রাম কন্টেন্টে বাধা সৃষ্টি করা উচিত নয়।

ছবি ১. লাইভ টিভি অ্যাপে ওভারলে মেসেজ।

এছাড়াও, কন্টেন্টের রেটিং এবং অভিভাবকীয় নিয়ন্ত্রণ সেটিংসের উপর ভিত্তি করে নির্দিষ্ট প্রোগ্রাম কন্টেন্ট দেখানো উচিত কিনা এবং কন্টেন্ট ব্লক করা বা উপলভ্য না থাকলে আপনার অ্যাপ কীভাবে কাজ করে এবং ব্যবহারকারীকে জানায় তা বিবেচনা করুন। এইসব বিষয় বিবেচনা করে কীভাবে আপনার টিভি ইনপুটের ব্যবহারকারীর অভিজ্ঞতা উন্নত করবেন তা এই টিউটোরিয়ালে বর্ণনা করা হয়েছে।

TV ইনপুট পরিষেবা স্যাম্পেল অ্যাপ ব্যবহার করে দেখুন।

সারফেসের সাথে প্লেয়ার ইন্টিগ্রেট করা

আপনার টিভি ইনপুটকে অবশ্যই Surface অবজেক্টে ভিডিও রেন্ডার করতে হবে, যা TvInputService.Session.onSetSurface() পদ্ধতির মাধ্যমে পাস করা হয়। Surface অবজেক্টে কন্টেন্ট চালানোর জন্য কীভাবে MediaPlayer ইনস্ট্যান্স ব্যবহার করতে হয় তার একটি উদাহরণ এখানে দেওয়া হল:

Kotlin

override fun onSetSurface(surface: Surface?): Boolean {
    player?.setSurface(surface)
    mSurface = surface
    return true
}

override fun onSetStreamVolume(volume: Float) {
    player?.setVolume(volume, volume)
    mVolume = volume
}

জাভা

@Override
public boolean onSetSurface(Surface surface) {
    if (player != null) {
        player.setSurface(surface);
    }
    mSurface = surface;
    return true;
}

@Override
public void onSetStreamVolume(float volume) {
    if (player != null) {
        player.setVolume(volume, volume);
    }
    mVolume = volume;
}

একইভাবে, ExoPlayer ব্যবহার করে কীভাবে এটি করবেন তা এখানে দেওয়া হল:

Kotlin

override fun onSetSurface(surface: Surface?): Boolean {
    player?.createMessage(videoRenderer)?.apply {
        type = MSG_SET_SURFACE
        payload = surface
        send()
    }
    mSurface = surface
    return true
}

override fun onSetStreamVolume(volume: Float) {
    player?.createMessage(audioRenderer)?.apply {
        type = MSG_SET_VOLUME
        payload = volume
        send()
    }
    mVolume = volume
}

জাভা

@Override
public boolean onSetSurface(@Nullable Surface surface) {
    if (player != null) {
        player.createMessage(videoRenderer)
                .setType(MSG_SET_SURFACE)
                .setPayload(surface)
                .send();
    }
    mSurface = surface;
    return true;
}

@Override
public void onSetStreamVolume(float volume) {
    if (player != null) {
        player.createMessage(videoRenderer)
                .setType(MSG_SET_VOLUME)
                .setPayload(volume)
                .send();
    }
    mVolume = volume;
}

ওভারলে ব্যবহার করা

সাবটাইটেল, মেসেজ, বিজ্ঞাপন বা MHEG-5 ডেটা ব্রডকাস্ট দেখানোর জন্য ওভারলে ব্যবহার করুন। ডিফল্ট হিসেবে, ওভারলে বন্ধ করা থাকে। আপনি সেশন তৈরি করার সময় TvInputService.Session.setOverlayViewEnabled(true) কল করে এটি চালু করতে পারবেন, যেমন নিম্নলিখিত উদাহরণে দেখানো হয়েছে:

Kotlin

override fun onCreateSession(inputId: String): Session =
        onCreateSessionInternal(inputId).apply {
            setOverlayViewEnabled(true)
            sessions.add(this)
        }

জাভা

@Override
public final Session onCreateSession(String inputId) {
    BaseTvInputSessionImpl session = onCreateSessionInternal(inputId);
    session.setOverlayViewEnabled(true);
    sessions.add(session);
    return session;
}

ওভারলের জন্য View অবজেক্ট ব্যবহার করুন, যা TvInputService.Session.onCreateOverlayView() থেকে রিটার্ন করা হয়েছে, যেমন এখানে দেখানো হয়েছে:

Kotlin

override fun onCreateOverlayView(): View =
        (context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater).run {
            inflate(R.layout.overlayview, null).apply {
                subtitleView = findViewById<SubtitleView>(R.id.subtitles).apply {
                    // Configure the subtitle view.
                    val captionStyle: CaptionStyleCompat =
                            CaptionStyleCompat.createFromCaptionStyle(captioningManager.userStyle)
                    setStyle(captionStyle)
                    setFractionalTextSize(captioningManager.fontScale)
                }
            }
        }

জাভা

@Override
public View onCreateOverlayView() {
    LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    View view = inflater.inflate(R.layout.overlayview, null);
    subtitleView = (SubtitleView) view.findViewById(R.id.subtitles);

    // Configure the subtitle view.
    CaptionStyleCompat captionStyle;
    captionStyle = CaptionStyleCompat.createFromCaptionStyle(
            captioningManager.getUserStyle());
    subtitleView.setStyle(captionStyle);
    subtitleView.setFractionalTextSize(captioningManager.fontScale);
    return view;
}

ওভারলের জন্য লেআউট ডেফিনিশন দেখতে এইরকম হতে পারে:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.exoplayer.text.SubtitleView
        android:id="@+id/subtitles"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="32dp"
        android:visibility="invisible"/>
</FrameLayout>

কন্টেন্ট নিয়ন্ত্রণ করুন

ব্যবহারকারী কোনও চ্যানেল বেছে নিলে, আপনার টিভি ইনপুট TvInputService.Session অবজেক্টে onTune() কলব্যাক ম্যানেজ করে। সিস্টেম TV অ্যাপের অভিভাবকীয় নিয়ন্ত্রণ, কন্টেন্ট রেটিংয়ের উপর ভিত্তি করে কোন কন্টেন্ট দেখানো হবে তা নির্ধারণ করে। নিচের বিভাগগুলি থেকে জানতে পারবেন যে সিস্টেম টিভি অ্যাপের সাথে যোগাযোগ করে এমন TvInputService.Session notify পদ্ধতি ব্যবহার করে কীভাবে চ্যানেল ও প্রোগ্রাম বেছে নেওয়ার বিষয়টি ম্যানেজ করতে হয়।

ভিডিও অনুপলভ্য করুন

ব্যবহারকারী চ্যানেল পরিবর্তন করলে, আপনার টিভি ইনপুট কন্টেন্ট রেন্ডার করার আগে স্ক্রিনে যাতে কোনও অপ্রাসঙ্গিক ভিডিও আর্টিফ্যাক্ট না দেখায় তা নিশ্চিত করতে চান। TvInputService.Session.onTune() কল করার সময়, আপনি TvInputService.Session.notifyVideoUnavailable() কল করে এবং VIDEO_UNAVAILABLE_REASON_TUNING কনস্ট্যান্ট পাস করে ভিডিও দেখানো আটকাতে পারবেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে।

Kotlin

override fun onTune(channelUri: Uri): Boolean {
    subtitleView?.visibility = View.INVISIBLE
    notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING)
    unblockedRatingSet.clear()

    dbHandler.apply {
        removeCallbacks(playCurrentProgramRunnable)
        playCurrentProgramRunnable = PlayCurrentProgramRunnable(channelUri)
        post(playCurrentProgramRunnable)
    }
    return true
}

জাভা

@Override
public boolean onTune(Uri channelUri) {
    if (subtitleView != null) {
        subtitleView.setVisibility(View.INVISIBLE);
    }
    notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
    unblockedRatingSet.clear();

    dbHandler.removeCallbacks(playCurrentProgramRunnable);
    playCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri);
    dbHandler.post(playCurrentProgramRunnable);
    return true;
}

তারপর, কন্টেন্ট Surface-এ রেন্ডার করা হলে, আপনি TvInputService.Session.notifyVideoAvailable() কল করে ভিডিও দেখানোর অনুমতি দেন, যেমন:

Kotlin

fun onRenderedFirstFrame(surface:Surface) {
    firstFrameDrawn = true
    notifyVideoAvailable()
}

জাভা

@Override
public void onRenderedFirstFrame(Surface surface) {
    firstFrameDrawn = true;
    notifyVideoAvailable();
}

এই ট্রানজিশন মাত্র কয়েক সেকেন্ডের জন্য থাকে, তবে ছবি ফ্ল্যাশ করার সময় অদ্ভুত ব্লিপ ও জিটার দেখানোর চেয়ে ফাঁকা স্ক্রিন দেখানো ভিজ্যুয়ালি অনেক ভালো।

এছাড়াও, ভিডিও রেন্ডার করার জন্য Surface-এর সাথে কাজ করার ব্যাপারে আরও তথ্য পেতে, সারফেসের সাথে প্লেয়ার ইন্টিগ্রেট করুন দেখুন।

অভিভাবকীয় নিয়ন্ত্রণ প্রদান করে

অভিভাবকীয় নিয়ন্ত্রণ এবং কন্টেন্ট রেটিংয়ের মাধ্যমে কোনও কন্টেন্ট ব্লক করা হয়েছে কিনা তা নির্ধারণ করতে, আপনি TvInputManager ক্লাস মেথড, isParentalControlsEnabled() এবং isRatingBlocked(android.media.tv.TvContentRating) চেক করুন। এছাড়াও, আপনি হয়ত নিশ্চিত করতে চাইবেন যে কন্টেন্টের TvContentRating বর্তমানে অনুমোদিত কন্টেন্ট রেটিংয়ের সেটে অন্তর্ভুক্ত করা হয়েছে। এইসব বিষয় নিচের স্যাম্পেলে দেখানো হয়েছে।

Kotlin

private fun checkContentBlockNeeded() {
    currentContentRating?.also { rating ->
        if (!tvInputManager.isParentalControlsEnabled
                || !tvInputManager.isRatingBlocked(rating)
                || unblockedRatingSet.contains(rating)) {
            // Content rating is changed so we don't need to block anymore.
            // Unblock content here explicitly to resume playback.
            unblockContent(null)
            return
        }
    }
    lastBlockedRating = currentContentRating
    player?.run {
        // Children restricted content might be blocked by TV app as well,
        // but TIF should do its best not to show any single frame of blocked content.
        releasePlayer()
    }

    notifyContentBlocked(currentContentRating)
}

জাভা

private void checkContentBlockNeeded() {
    if (currentContentRating == null || !tvInputManager.isParentalControlsEnabled()
            || !tvInputManager.isRatingBlocked(currentContentRating)
            || unblockedRatingSet.contains(currentContentRating)) {
        // Content rating is changed so we don't need to block anymore.
        // Unblock content here explicitly to resume playback.
        unblockContent(null);
        return;
    }

    lastBlockedRating = currentContentRating;
    if (player != null) {
        // Children restricted content might be blocked by TV app as well,
        // but TIF should do its best not to show any single frame of blocked content.
        releasePlayer();
    }

    notifyContentBlocked(currentContentRating);
}

কন্টেন্ট ব্লক করা উচিত নাকি উচিত নয় তা নির্ধারণ করার পরে, আগের উদাহরণে দেখানো TvInputService.Session পদ্ধতি notifyContentAllowed() বা notifyContentBlocked() কল করে সিস্টেম TV অ্যাপকে জানান।

TvContentRating ক্লাস ব্যবহার করে COLUMN_CONTENT_RATING-এর জন্য সিস্টেম-নির্ধারিত স্ট্রিং তৈরি করুন, TvContentRating.createRating() পদ্ধতি ব্যবহার করে, যেমন এখানে দেখানো হয়েছে:

Kotlin

val rating = TvContentRating.createRating(
        "com.android.tv",
        "US_TV",
        "US_TV_PG",
        "US_TV_D", "US_TV_L"
)

জাভা

TvContentRating rating = TvContentRating.createRating(
    "com.android.tv",
    "US_TV",
    "US_TV_PG",
    "US_TV_D", "US_TV_L");

ট্র্যাক বেছে নেওয়া ম্যানেজ করুন

TvTrackInfo ক্লাস মিডিয়া ট্র্যাকের তথ্য রাখে, যেমন ট্র্যাকের ধরন (ভিডিও, অডিও বা সাবটাইটেল) ইত্যাদি।

প্রথমবার আপনার টিভি ইনপুট সেশন ট্র্যাকের তথ্য পেলে, সিস্টেম টিভি অ্যাপ আপডেট করার জন্য সব ট্র্যাকের তালিকা সহ TvInputService.Session.notifyTracksChanged() কল করা উচিত। ট্র্যাকের তথ্যে কোনও পরিবর্তন হলে, সিস্টেম আপডেট করার জন্য আবার notifyTracksChanged() কল করুন।

সিস্টেম TV অ্যাপ ব্যবহারকারীকে নির্দিষ্ট ট্র্যাক বেছে নেওয়ার জন্য ইন্টারফেস প্রদান করে, যদি প্রদত্ত ট্র্যাকের জন্য একাধিক ট্র্যাক উপলভ্য থাকে; যেমন, বিভিন্ন ভাষায় সাবটাইটেল। আপনার টিভি ইনপুট onSelectTrack() সিস্টেম টিভি অ্যাপ থেকে কল করার মাধ্যমে notifyTrackSelected() কলের উত্তর দেয়, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে। মনে রাখবেন যে ট্র্যাক আইডি হিসেবে null পাস করা হলে, এটি ট্র্যাকটিকে অনির্বাচন করে।

Kotlin

override fun onSelectTrack(type: Int, trackId: String?): Boolean =
        mPlayer?.let { player ->
            if (type == TvTrackInfo.TYPE_SUBTITLE) {
                if (!captionEnabled && trackId != null) return false
                selectedSubtitleTrackId = trackId
                subtitleView.visibility = if (trackId == null) View.INVISIBLE else View.VISIBLE
            }
            player.trackInfo.indexOfFirst { it.trackType == type }.let { trackIndex ->
                if( trackIndex >= 0) {
                    player.selectTrack(trackIndex)
                    notifyTrackSelected(type, trackId)
                    true
                } else false
            }
        } ?: false

জাভা

@Override
public boolean onSelectTrack(int type, String trackId) {
    if (player != null) {
        if (type == TvTrackInfo.TYPE_SUBTITLE) {
            if (!captionEnabled && trackId != null) {
                return false;
            }
            selectedSubtitleTrackId = trackId;
            if (trackId == null) {
                subtitleView.setVisibility(View.INVISIBLE);
            }
        }
        int trackIndex = -1;
        MediaPlayer.TrackInfo[] trackInfos = player.getTrackInfo();
        for (int index = 0; index < trackInfos.length; index++) {
            MediaPlayer.TrackInfo trackInfo = trackInfos[index];
            if (trackInfo.getTrackType() == type) {
                trackIndex = index;
                break;
            }
        }
        if (trackIndex >= 0) {
            player.selectTrack(trackIndex);
            notifyTrackSelected(type, trackId);
            return true;
        }
    }
    return false;
}