टीवी उपयोगकर्ता इंटरैक्शन को मैनेज करें

लाइव टीवी देखते समय, उपयोगकर्ता चैनल बदलता है. इसके बाद, उसे चैनल और प्रोग्राम की जानकारी कुछ समय के लिए दिखती है. इसके बाद, यह जानकारी गायब हो जाती है. अन्य तरह की जानकारी, जैसे कि मैसेज ("इसे घर पर न आज़माएं"), सबटाइटल या विज्ञापनों को बनाए रखने की ज़रूरत पड़ सकती है. किसी भी टीवी ऐप्लिकेशन की तरह, इस तरह की जानकारी से स्क्रीन पर चल रहे प्रोग्राम के कॉन्टेंट में रुकावट नहीं आनी चाहिए.

पहली इमेज. लाइव टीवी ऐप्लिकेशन में ओवरले मैसेज.

यह भी ध्यान रखें कि कॉन्टेंट की रेटिंग और माता-पिता के कंट्रोल की सेटिंग के हिसाब से, प्रोग्राम का कुछ कॉन्टेंट दिखाया जाना चाहिए या नहीं. साथ ही, यह भी ध्यान रखें कि कॉन्टेंट ब्लॉक होने या उपलब्ध न होने पर, आपका ऐप्लिकेशन कैसे काम करता है और उपयोगकर्ता को इसकी जानकारी कैसे देता है. इस सबक में, इन बातों को ध्यान में रखते हुए, टीवी इनपुट के उपयोगकर्ता अनुभव को बेहतर बनाने का तरीका बताया गया है.

टीवी इनपुट सेवा का सैंपल ऐप्लिकेशन आज़माएं.

प्लेयर को प्लैटफ़ॉर्म के साथ इंटिग्रेट करना

आपके टीवी इनपुट को वीडियो को 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
}

Java

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

Java

@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)
        }

Java

@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)
                }
            }
        }

Java

@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 कॉन्स्टेंट पास करके, वीडियो को प्रज़ेंट होने से रोका जा सकता है. इसका उदाहरण यहां दिया गया है.

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
}

Java

@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()
}

Java

@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)
}

Java

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 क्लास का इस्तेमाल करें. जैसा कि यहां दिखाया गया है:

Kotlin

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

Java

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

ट्रैक चुनने की सुविधा को मैनेज करना

TvTrackInfo क्लास मीडिया ट्रैक के बारे में जानकारी रखती है जैसे कि ट्रैक का प्रकार (वीडियो, ऑडियो या उपशीर्षक) इत्यादि.

जब टीवी इनपुट सेशन को पहली बार ट्रैक की जानकारी मिलती है, तब उसे TvInputService.Session.notifyTracksChanged() को कॉल करना चाहिए. इसमें सभी ट्रैक की सूची शामिल होती है, ताकि सिस्टम टीवी ऐप्लिकेशन को अपडेट किया जा सके. जब ट्रैक की जानकारी में कोई बदलाव होता है, तब सिस्टम को अपडेट करने के लिए, TvInputService.Session.notifyTracksChanged() को फिर से कॉल करें.notifyTracksChanged()

सिस्टम टीवी ऐप उपयोगकर्ता को एक विशिष्ट ट्रैक का चयन करने के लिए एक इंटरफ़ेस प्रदान करता है यदि किसी दिए गए ट्रैक प्रकार के लिए एक से अधिक ट्रैक उपलब्ध हैं; उदाहरण के लिए, विभिन्न भाषाओं में उपशीर्षक. आपका टीवी इनपुट सिस्टम टीवी ऐप से 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

Java

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