টিভি ব্যবহারকারীর মিথস্ক্রিয়া পরিচালনা করুন

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

চিত্র 1. একটি লাইভ টিভি অ্যাপে একটি ওভারলে বার্তা৷

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

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

পৃষ্ঠের সাথে প্লেয়ারকে সংহত করুন

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

কোটলিন

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 ব্যবহার করে এটি কীভাবে করবেন তা এখানে:

কোটলিন

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) কল করে এটি সক্রিয় করতে পারেন, যেমনটি নিম্নলিখিত উদাহরণে রয়েছে:

কোটলিন

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

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

কোটলিন

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() কলব্যাক পরিচালনা করে। সিস্টেম টিভি অ্যাপের অভিভাবকীয় নিয়ন্ত্রণগুলি বিষয়বস্তু রেটিং দেওয়া হলে কি সামগ্রী প্রদর্শন করবে তা নির্ধারণ করে৷ নিম্নলিখিত বিভাগগুলি বর্ণনা করে যে কীভাবে চ্যানেল এবং প্রোগ্রাম নির্বাচন পরিচালনা করতে হয় TvInputService.Session notify পদ্ধতিগুলি যা সিস্টেম টিভি অ্যাপের সাথে যোগাযোগ করে।

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

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

কোটলিন

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() কল করুন, যেমন:

কোটলিন

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 বর্তমানে অনুমোদিত সামগ্রী রেটিংগুলির একটি সেটের মধ্যে অন্তর্ভুক্ত রয়েছে৷ এই বিবেচনাগুলি নিম্নলিখিত নমুনায় দেখানো হয়েছে।

কোটলিন

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() কল করে সিস্টেম টিভি অ্যাপকে অবহিত করুন, যেমনটি আগের উদাহরণে দেখানো হয়েছে৷

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

কোটলিন

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() কে আবার কল করুন।

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

কোটলিন

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