Wyświetlanie podglądu filmów

Film z podglądem to świetny sposób na zachęcenie użytkowników do dodania precyzyjnych linków do Twojej aplikacji telewizyjnej. Podglądy mogą być różne – od krótkich klipów po pełne zwiastuny filmów.

Podczas tworzenia podglądu pamiętaj o tych wskazówkach:

  • Nie wyświetlaj reklam w podglądzie. Jeśli zszywasz reklamy po stronie klienta, nie łącz ich w filmy z podglądem. Jeśli umieścisz reklamy po stronie serwera, udostępnij podgląd filmu bez reklam.
  • Aby uzyskać najlepszą jakość, podgląd filmu powinien mieć proporcje 16:9 lub 4:3. Zalecane rozmiary filmów z podglądem znajdziesz w artykule Atrybuty programu wideo.
  • Gdy film z podglądem i plakat mają różne formaty obrazu, przed odtworzeniem podglądu rozmiar widoku plakatu zostanie zmieniony na odpowiedni o współczynniku proporcji filmu. W filmie nie ma czarnych pasów. Jeśli na przykład współczynnik plakatu wynosi ASPECT_RATIO_MOVIE_POSTER (1:1,441), a współczynnik filmu to 16:9, widok plakatu zmieni się na 16:9.
  • Po utworzeniu podglądu jego zawartość może być dostępna publicznie lub chroniona DRM. W każdym przypadku obowiązują inne procedury. Na tej stronie opisujemy oba te tematy.

Odtwórz podgląd na ekranie głównym

Jeśli utworzysz podgląd, używając dowolnego z typów filmów obsługiwanych przez ExoPlayer, który jest publicznie dostępny, możesz odtworzyć go bezpośrednio na ekranie głównym.

Podczas tworzenia programu PreviewProgram użyj parametru setPreviewVideoUri() z publicznie dostępnym adresem URL HTTPS, jak pokazano w poniższym przykładzie. Podgląd może obejmować wideo lub audio.

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 film jest chroniony DRM lub znajduje się w źródle treści, który nie jest obsługiwany przez ExoPlayer, użyj TvInputService. Ekran główny Androida TV przekazuje Surface do Twojej usługi, wywołując onSetSurface(). Aplikacja rysuje film bezpośrednio na tej platformie z onTune().

Bezpośrednie renderowanie powierzchni pozwala aplikacji kontrolować, co i jak jest renderowane. Możesz nałożyć na siebie metadane, takie jak nazwa kanału.

Zadeklaruj usługę TvInputService w pliku manifestu

Aby na ekranie głównym wyświetlał się podgląd, Twoja aplikacja musi udostępniać implementację pakietu TvInputService.

W deklaracji usługi umieść filtr intencji, który wskazuje TvInputService jako działanie, które ma zostać wykonane z zamiarem. Zadeklaruj także metadane usługi jako oddzielny zasób XML. Deklaracja usługi, filtr intencji i deklaracja metadanych usługi znajdują się 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 odpowiadać nazwie zasobu zadeklarowanego w pliku manifestu. Korzystając z wpisów z poprzedniego przykładu, utwórz plik XML pod adresem res/xml/previewinputservice.xml z pustym tagiem tv-input:

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

Platforma wejść telewizyjnych musi zawierać ten tag. Służy on jednak 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 ma być renderowany przez aplikację, a nie ekran główny Androida TV, musisz utworzyć identyfikator URI filmu dla elementu PreviewProgram. Identyfikator URI powinien kończyć się identyfikatorem, którego aplikacja używa na potrzeby treści, aby umożliwić późniejsze pobranie treści za pomocą polecenia TvInputService.

Jeśli identyfikator to 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 Twój identyfikator nie jest typu Long, utwórz identyfikator URI przy użyciu 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();

Aplikacja wywołuje funkcję onTune(Uri videoUri), aby umożliwić Androida TV uruchomienie filmu z podglądem.

Tworzenie usługi

Z przykładu poniżej dowiesz się, jak rozszerzyć pole TvInputService, by utworzyć własną właściwość PreviewInputService. Pamiętaj, że usługa używa do odtwarzania elementu MediaPlayer, ale Twój kod może korzystać z 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/
            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/
            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
        }
    }
}