TV-Nutzerinteraktion verwalten

Beim Live-TV wechselt der Nutzer den Kanal und es wird eine Kanal- und Programminformationen kurz, bevor die Informationen verschwinden. Andere Arten von Informationen, wie z. B. Nachrichten ("DO NOT ATTEMPT AT HOME"), Untertitel oder Werbung. Wie bei jedem Fernseher App dürfen diese Informationen die auf dem Bildschirm wiedergegebenen Programminhalte nicht beeinträchtigen.

Abbildung 1: Eine Overlay-Nachricht in einer Live-TV-App

Überlegen Sie auch, ob bestimmte Programminhalte angesichts des die Altersfreigabe und die Jugendschutzeinstellungen sowie das Verhalten Ihrer App und Informationen Inhalte blockiert sind oder nicht verfügbar sind. In dieser Lektion wird beschrieben, wie Sie den Nutzer Erfahrung für diese Überlegungen.

Probieren Sie die aus. Beispiel-App „TV Input Service“

Player in die Oberfläche integrieren

Deine TV-Eingabe muss das Video in einem Surface-Objekt rendern, das über die TvInputService.Session.onSetSurface() . Hier ist ein Beispiel, wie eine MediaPlayer-Instanz zum Spielen verwendet wird. Inhalt im Surface-Objekt:

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

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

Overlay verwenden

Mit einem Overlay können Sie Untertitel, Nachrichten, Werbung oder MHEG-5-Datensendungen einblenden. Standardmäßig enthält der Parameter Overlay ist deaktiviert. Sie können sie beim Erstellen der Sitzung durch folgenden Aufruf aktivieren: TvInputService.Session.setOverlayViewEnabled(true), wie im folgenden Beispiel:

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

Verwenden Sie für das Overlay ein View-Objekt, das wie hier gezeigt von TvInputService.Session.onCreateOverlayView() zurückgegeben wird:

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

Die Layout-Definition für das Overlay könnte wie folgt aussehen:

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

Inhalt steuern

Wenn der Nutzer einen Kanal auswählt, verarbeitet der TV-Eingang den onTune()-Callback im TvInputService.Session-Objekt. Der Systemfernseher Die Jugendschutzeinstellungen der App bestimmen, welche Inhalte basierend auf der Altersfreigabe angezeigt werden. In den folgenden Abschnitten wird beschrieben, wie du die Kanal- und Programmauswahl mithilfe der TvInputService.Session notify-Methoden, die mit der TV-App des Systems kommunizieren.

Video nicht mehr verfügbar machen

Wenn der Nutzer den Kanal wechselt, möchten Sie sicherstellen, dass auf dem Bildschirm keine Videoartefakte, bevor die Inhalte auf dem Fernseher gerendert werden. Wenn Sie TvInputService.Session.onTune() anrufen, kannst du verhindern, dass das Video präsentiert wird, indem du TvInputService.Session.notifyVideoUnavailable() aufrufst. und die Konstante VIDEO_UNAVAILABLE_REASON_TUNING übergeben, wie im folgenden Beispiel dargestellt.

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

Wenn der Inhalt dann im Surface gerendert wird, rufen Sie TvInputService.Session.notifyVideoAvailable() um das Video anzuzeigen. Beispiel:

Kotlin

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

Java

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

Dieser Übergang dauert nur den Bruchteil einer Sekunde. visuell besser, als seltsame Bildblasen zu blinken.

Weitere Informationen zur Arbeit findest du unter Player in Oberfläche integrieren. mit Surface, um das Video zu rendern.

Jugendschutzeinstellungen anbieten

Um herauszufinden, ob bestimmte Inhalte durch die Jugendschutzeinstellungen und die Altersfreigabe blockiert sind, sehen Sie sich das TvInputManager Klassenmethoden, isParentalControlsEnabled() und isRatingBlocked(android.media.tv.TvContentRating). Ich Sie sollten auch sicherstellen, dass das TvContentRating des Inhalts in einem der aktuell zulässigen Altersfreigaben. Diese Überlegungen werden im folgenden Beispiel gezeigt.

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

Benachrichtigen Sie den Systemfernseher, nachdem Sie festgestellt haben, ob die Inhalte blockiert werden sollen oder nicht. indem Sie die Methode TvInputService.Session-Methode notifyContentAllowed() oder notifyContentBlocked() aus, wie im vorherigen Beispiel gezeigt.

Verwenden Sie die Klasse TvContentRating, um den systemdefinierten String für den COLUMN_CONTENT_RATING durch den TvContentRating.createRating() wie hier gezeigt:

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

Titelauswahl verwalten

Die Klasse TvTrackInfo enthält Informationen zu Medien-Tracks wie als Tracktyp (Video, Audio oder Untertitel) usw.

Wenn Ihre TV-Eingabesitzung zum ersten Mal Titelinformationen abrufen kann, sollte Folgendes aufgerufen werden: TvInputService.Session.notifyTracksChanged() durch eine Liste aller Titel zum Aktualisieren der System-TV-App. Wenn ich dort eine Änderung der Titelinformationen, notifyTracksChanged() um das System zu aktualisieren.

Die System-TV-App bietet eine Oberfläche, über die Nutzer einen bestimmten Titel auswählen können, falls mehrere Titel für einen bestimmten Track-Typ verfügbar ist, zum Beispiel Untertitel in verschiedenen Sprachen. Mein Fernseher die Eingabe auf den onSelectTrack() über die System-TV-App aufrufen, indem Sie notifyTrackSelected() aus, wie im folgenden Beispiel gezeigt. Hinweis: Wenn null als Track-ID übergeben wird, wird die Auswahl für den Track aufgehoben.

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