TV kullanıcı etkileşimini yönetme

Canlı TV deneyiminde, kullanıcı kanalları değiştirir ve bilgiler kaybolmadan önce kısa süreliğine kanal ve program bilgilerini alır. Diğer bilgi türleri (ör. "EVDE ATMAYIN"), altyazıların veya reklamların devam etmesi gerekebilir. Tüm TV'lerde olduğu gibi Bu tür bilgiler ekranda oynatılan program içeriğini engellememelidir.

Şekil 1. Canlı TV uygulamasında yer paylaşımlı mesaj.

Ayrıca göz önünde bulundurulduğunda, program içeriğinin Ayrıca, uygulamanızın ne zaman davrandığı ve bilgilendirdiği, içeriğin derecelendirmesi ve ebeveyn denetimi ayarları gibi engellenmiş veya kullanılamıyor. Bu derste, TV girişinizin kullanıcısını nasıl geliştireceğiniz açıklanmaktadır. deneyim kazanacaksınız.

Şunu deneyin: TV Giriş Hizmeti örnek uygulaması.

Oynatıcıyı yüzeyle entegre et

TV girişiniz, videoyu bir Surface nesnesinde oluşturmalıdır ve bu nesne TvInputService.Session.onSetSurface() yöntemidir. Aşağıda, oynatma için MediaPlayer örneğinin nasıl kullanılacağına dair bir örnek verilmiştir Surface nesnesindeki içerik:

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

Benzer bir şekilde, 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;
}

Bindirme kullan

Altyazıları, mesajları, reklamları veya MHEG-5 veri yayınlarını görüntülemek için yer paylaşımı kullanın. Varsayılan olarak yer paylaşımı devre dışı bırakıldı. Oturumu oluştururken TvInputService.Session.setOverlayViewEnabled(true), aşağıdaki örnekte olduğu gibi:

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

Yer paylaşımı için, aşağıda gösterildiği gibi TvInputService.Session.onCreateOverlayView() öğesinden döndürülen bir View nesnesi kullanın:

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

Yer paylaşımının düzen tanımı aşağıdaki gibi görünebilir:

<?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>

İçeriği denetleyin

Kullanıcı bir kanal seçtiğinde TV girişiniz TvInputService.Session nesnesindeki onTune() geri çağırmasını işler. Sistem TV'si Uygulamanın ebeveyn denetimleri, içerik derecelendirmesine göre hangi içeriğin gösterileceğini belirler. Aşağıdaki bölümlerde, TvInputService.Session notify yöntemi TV uygulamasıyla iletişim kurmalısınız.

Videoyu kullanılamaz hale getir

Kullanıcı kanalı değiştirdiğinde, ekranın başka bir kanalı izlemediğinden emin olmak içerik oluşturmadan önceki video bozukluklarını düzeltin. TvInputService.Session.onTune() adlı kişiyi aradığınızda, TvInputService.Session.notifyVideoUnavailable() numaralı telefonu arayarak videonun sunulmasını engelleyebilirsiniz VIDEO_UNAVAILABLE_REASON_TUNING sabitini belirtmek için aşağıdaki örnekte gösterilmiştir.

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

Ardından, içerik Surface için oluşturulduğunda TvInputService.Session.notifyVideoAvailable() ayarlamak için aşağıdaki gibi birini kullanabilirsiniz:

Kotlin

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

Java

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

Bu geçiş yalnızca saliselerce sürer ancak boş ekran göstermek görüntüde tuhaf görüntülerin ve titremelerin yanıp sönmesine izin vermekten daha iyidir.

Çalışma hakkında daha fazla bilgi için Oynatıcıyı yüzeyle entegre etme bölümüne de bakın. Videoyu oluşturmak için Surface ile.

Ebeveyn denetimi sağlama

Belirli bir içeriğin ebeveyn denetimleri ve içerik derecelendirmesi tarafından engellenip engellenmediğini öğrenmek için TvInputManager sınıf yöntemi, isParentalControlsEnabled() ve isRatingBlocked(android.media.tv.TvContentRating). Siz içeriğin TvContentRating öğesinin şu anda izin verilen içerik derecelendirmeleri grubudur. Bu hususlar aşağıdaki örnekte gösterilmiştir.

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

İçeriğin engellenip engellenmemesi gerektiğini belirledikten sonra, sistem TV'yi bilgilendirin uygulamasını çağırarak TvInputService.Session yöntemi notifyContentAllowed() veya notifyContentBlocked() olarak ayarlayın.

TvContentRating sınıfını kullanarak şunun için sistem tanımlı dizeyi oluşturun: COLUMN_CONTENT_RATING ve TvContentRating.createRating() yöntemini kullanabilirsiniz:

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");

Parça seçimini yönetme

TvTrackInfo sınıfı, medya kanallarıyla ilgili bilgiler içerir. Örneğin: ses veya altyazı gibi parçalardan birini seçin.

TV giriş oturumunuz parça bilgilerini ilk kez alabildiğinde şunu çağırmalıdır: Sistem TV uygulamasını güncellemek için tüm parçaların listesini içeren TvInputService.Session.notifyTracksChanged(). Oradayken takip bilgilerinde bir değişiklik, notifyTracksChanged() tekrar tıklayın.

Sistem TV uygulaması, birden fazla kanal varsa kullanıcının belirli bir kanalı seçmesi için bir arayüz sağlar. Belirli bir parça türü için kullanılabilir parça; Örneğin, farklı dillerdeki altyazılar. TV'niz giriş, onSelectTrack(). telefon ederek sistem TV uygulamasından arama notifyTrackSelected() (aşağıdaki örnekte gösterildiği gibi) ile uyumlu olmalıdır. null parametresi, parça kimliği olarak aktarılırsa parçanın seçimi kaldırılır.

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