Wyświetlanie podglądu filmów

Film z podglądem to świetny sposób na zachęcenie użytkowników do korzystania z precyzyjnych linków w Twojej aplikacji TV. Podglądy mogą obejmować od krótkich klipów po pełne filmy.

Podczas tworzenia podglądu pamiętaj o tych wytycznych:

  • Nie wyświetlaj reklam w podglądzie. Jeśli łączysz reklamy po stronie klienta, nie łącz ich z filmami z podglądem. Jeśli łączysz reklamy po stronie serwera, udostępnij film bez reklam na potrzeby podglądów.
  • Aby uzyskać najlepszą jakość, filmy z podglądem powinny mieć format 16:9 lub 4:3. Zalecane rozmiary filmów z podglądem znajdziesz w artykule Atrybuty programu wideo.
  • Gdy film z podglądem i grafika plakatu mają różne proporcje, ekran główny zmienia rozmiar widoku plakatu do proporcji filmu przed odtworzeniem podglądu. Film nie jest wyświetlany w formacie letterbox. Jeśli na przykład proporcje grafiki plakatu to ASPECT_RATIO_MOVIE_POSTER (1:1,441), ale proporcje filmu to 16:9, widok plakatu zmieni się w obszar o proporcjach 16:9.
  • Podczas tworzenia podglądu jego treść może być publicznie dostępna lub chroniona przez DRM. W każdym przypadku obowiązują inne procedury. Na tej stronie opisujemy obie.

Odtwarzanie podglądu na ekranie głównym

Jeśli utworzysz podgląd przy użyciu dowolnego typu filmu obsługiwanego przez ExoPlayer i podgląd jest publicznie dostępny, możesz odtwworzyć go bezpośrednio na ekranie głównym.

Podczas tworzenia PreviewProgram użyj setPreviewVideoUri() z publicznie dostępnym adresem URL HTTPS, jak pokazano w tym przykładzie. Podgląd może być albo filmem albo dźwiękiem.

Kotlin

val previewVideoUrl = Uri.parse("https://www.example.com/preview.mp4")
val builder = PreviewProgram.Builder()
builder.setChannelId(channelId)
    // ...
    .setPreviewVideoUri(previewVideoUrl)

Java

Uri previewVideoUrl = Uri.parse("https://www.example.com/preview.mp4");
PreviewProgram.Builder builder = new PreviewProgram.Builder();
builder.setChannelId(channelId)
    // ...
    .setPreviewVideoUri(Uri.parse(previewVideoUrl));

Renderowanie podglądu na powierzchni

Jeśli Twój film jest chroniony przez DRM lub ma typ multimediów nieobsługiwany przez ExoPlayer, użyj TvInputService. Ekran główny Androida TV przekazuje a Surface do Twojej usługi, wywołując onSetSurface(). Twoja aplikacja rysuje film bezpośrednio na tej powierzchni z onTune().

Bezpośrednie renderowanie powierzchni pozwala aplikacji kontrolować, co i jak jest renderowane. Możesz nakładać metadane, takie jak atrybucja kanału.

Deklarowanie TvInputService w pliku manifestu

Twoja aplikacja musi udostępniać implementację TvInputService, aby ekran główny mógł renderować podgląd.

W deklaracji usługi umieść filtr intencji, który określa TvInputService jako działanie do wykonania z intencją. Zadeklaruj też metadane usługi jako osobny zasób XML. Deklaracja usługi, filtr intencji i deklaracja metadanych usługi są pokazane w tym przykładzie:

<service android:name=".rich.PreviewInputService"
    android:permission="android.permission.BIND_TV_INPUT">
    <!-- Required filter used by the system to launch our account service. -->
    <intent-filter>
        <action android:name="android.media.tv.TvInputService" />
    </intent-filter>
    <!-- An XML file which describes this input. -->
    <meta-data
        android:name="android.media.tv.input"
        android:resource="@xml/previewinputservice" />
</service>

Zdefiniuj metadane usługi w osobnym pliku XML. Plik metadanych usługi znajduje się w katalogu zasobów XML aplikacji i musi być zgodny z nazwą zasobu zadeklarowanego w pliku manifestu. Korzystając z wpisów w pliku manifestu z poprzedniego przykładu, utwórz plik XML w res/xml/previewinputservice.xml z pustym tagiem tv-input:

<?xml version="1.0" encoding="utf-8"?>
<tv-input/>

TV Input Framework musi mieć ten tag. Jest on jednak używany tylko do konfigurowania kanałów na żywo. Ponieważ renderujesz film, tag powinien być pusty.

Tworzenie identyfikatora URI filmu

Aby wskazać, że film z podglądem powinien być renderowany przez Twoją aplikację, a nie przez ekran główny Androida TV, musisz utworzyć identyfikator URI filmu dla PreviewProgram. Identyfikator URI powinien kończyć się identyfikatorem, którego Twoja aplikacja używa do określania treści, aby można było później pobrać treść w TvInputService.

Jeśli identyfikator ma typ Long, użyj TvContractCompat.buildPreviewProgramUri():

Kotlin

val id: Long = 1L // content identifier
val componentName = new ComponentName(context, PreviewVideoInputService.class)
val previewProgramVideoUri = TvContractCompat.buildPreviewProgramUri(id)
   .buildUpon()
   .appendQueryParameter("input", TvContractCompat.buildInputId(componentName))
   .build()

Java

Long id = 1L; // content identifier
ComponentName componentName = new ComponentName(context, PreviewVideoInputService.class);
previewProgramVideoUri = TvContractCompat.buildPreviewProgramUri(id)
       .buildUpon()
       .appendQueryParameter("input", TvContractCompat.buildInputId(componentName))
       .build();

Jeśli identyfikator nie ma typu Long, utwórz identyfikator URI za pomocą Uri.withAppendedPath():

Kotlin

val previewProgramVideoUri = Uri.withAppendedPath(PreviewPrograms.CONTENT_URI, "content-identifier")
       .buildUpon()
       .appendQueryParameter("input", TvContractCompat.buildInputId(componentName))
       .build()

Java

previewProgramVideoUri = Uri.withAppendedPath(PreviewPrograms.CONTENT_URI, "content-identifier")
       .buildUpon()
       .appendQueryParameter("input", TvContractCompat.buildInputId(componentName))
       .build();

Twoja aplikacja wywołuje onTune(Uri videoUri) , aby Android TV rozpoczął odtwarzanie filmu z podglądem.

Tworzenie usługi

Ten przykład pokazuje, jak rozszerzyć TvInputService, aby utworzyć własną PreviewInputService. Pamiętaj, że usługa używa MediaPlayer do odtwarzania, ale Twój kod może używać dowolnego dostępnego odtwarzacza wideo.

Kotlin

import android.content.Context
import android.media.MediaPlayer
import android.media.tv.TvInputService
import android.net.Uri
import android.util.Log
import android.view.Surface
import java.io.IOException

class PreviewVideoInputService : TvInputService() {

    override fun onCreateSession(inputId: String): TvInputService.Session? {
        return PreviewSession(this)
    }

    private inner class PreviewSession(
        internal var context: Context
    ) : TvInputService.Session(context) {

        internal var mediaPlayer: MediaPlayer? = MediaPlayer()

        override fun onRelease() {
            mediaPlayer?.release()
            mediaPlayer = null
        }

        override fun onTune(uri: Uri): Boolean {
            // Let the TvInputService know that the video is being loaded.
            notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_TUNING)
            // Fetch the stream url from the TV Provider database
            // for content://android.media.tv/preview_program/<id>
            val id = uri.lastPathSegment
            // Load your video in the background.
            retrieveYourVideoPreviewUrl(id) { videoUri ->
                if (videoUri == null) {
                  Log.d(TAG, "Could not find video $id")
                  notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN)
                }

                try {
                    mPlayer.setDataSource(getApplicationContext(), videoUri)
                    mPlayer.prepare()
                    mPlayer.start()
                    notifyVideoAvailable()
                } catch (IOException e) {
                    Log.e(TAG, "Could not prepare media player", e)
                    notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN)
                }
              }
          return true
        }

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

        override fun onSetStreamVolume(volume: Float) {
            // The home screen may fade in and out the video's volume.
            // Your player should be updated accordingly.
            mediaPlayer?.setVolume(volume, volume)
        }

        override fun onSetCaptionEnabled(b: Boolean) {
            // enable/disable captions here
        }
    }

    companion object {
        private const val TAG = "PreviewInputService"
    }
}

Java

import android.content.Context;
import android.media.MediaPlayer;
import android.media.tv.TvInputService;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.Surface;
import java.io.IOException;

public class PreviewVideoInputService extends TvInputService {
    private static final String TAG = "PreviewVideoInputService";

    @Nullable
    @Override
    public Session onCreateSession(String inputId) {
        return new PreviewSession(this);
    }

    private class PreviewSession extends TvInputService.Session {

        private MediaPlayer mPlayer;

        PreviewSession(Context context) {
            super(context);
            mPlayer = new MediaPlayer();
        }

        @Override
        public boolean onTune(Uri channelUri) {
            // Let the TvInputService know that the video is being loaded.
            notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_TUNING);
            // Fetch the stream url from the TV Provider database
            // for content://android.media.tv/preview_program/<id>
            String id = uri.getLastPathSegment();
            // Load your video in the background.
            retrieveYourVideoPreviewUrl(id, new MyCallback() {
              public void callback(Uri videoUri) {
                if (videoUri == null) {
                  Log.d(TAG, "Could not find video" + id);
                  notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
                }

                try {
                    mPlayer.setDataSource(getApplicationContext(), videoUri);
                    mPlayer.prepare();
                    mPlayer.start();
                    notifyVideoAvailable();
                } catch (IOException e) {
                    Log.e(TAG, "Could not prepare media player", e);
                    notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
                }
              }
            });
            return true;
        }

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

        @Override
        public void onRelease() {
            if (mPlayer != null) {
                mPlayer.release();
            }
            mPlayer = null;
        }

        @Override
        public void onSetStreamVolume(float volume) {
            if (mPlayer != null) {
                // The home screen may fade in and out the video's volume.
                // Your player should be updated accordingly.
                mPlayer.setVolume(volume, volume);
            }
        }

        @Override
        public void onSetCaptionEnabled(boolean enabled) {
            // enable/disable captions here
        }
    }
}