תצוגה מקדימה של סרטונים

סרטון לתצוגה מקדימה הוא דרך מצוינת לעודד משתמשים לקשר עמוק לאפליקציית הטלוויזיה שלכם. סרטונים לתצוגה מקדימה יכולים להיות קליפים קצרים או טריילרים של סרטים מלאים.

כשיוצרים תצוגה מקדימה, כדאי לפעול לפי ההנחיות הבאות:

  • לא להציג מודעות בתצוגה מקדימה. אם אתם מצמידים מודעות בצד הלקוח, אל תצמידו אותן לסרטוני תצוגה מקדימה. אם אתם מצמידים מודעות בצד השרת, עליכם לספק סרטון ללא מודעות לתצוגות מקדימות.
  • כדי לקבל את האיכות הכי טובה, יחס הגובה-רוחב של סרטוני התצוגה המקדימה צריך להיות 16:9 או 4:3. במאמר בנושא מאפייני תוכנית הווידאו מפורטים הגדלים המומלצים של סרטוני תצוגה מקדימה.
  • אם יחס הגובה-רוחב של סרטון התצוגה המקדימה שונה מיחס הגובה-רוחב של התמונה הממוזערת, מסך הבית ישנה את הגודל של התמונה הממוזערת כך שיתאים ליחס הגובה-רוחב של הסרטון לפני הפעלת התצוגה המקדימה. הסרטון לא מוצג בפורמט letterbox. לדוגמה, אם יחס הגובה-רוחב של תמונת הפוסטר הוא ASPECT_RATIO_MOVIE_POSTER (1:1.441) אבל יחס הגובה-רוחב של הסרטון הוא 16:9, תצוגת הפוסטר תשתנה לאזור ביחס גובה-רוחב של 16:9.
  • כשיוצרים תצוגה מקדימה, התוכן שלה יכול להיות נגיש לציבור או מוגן על ידי DRM. בכל מקרה חלים נהלים שונים. בדף הזה מוסבר על שניהם.

הפעלת התצוגה המקדימה במסך הבית

אם יוצרים תצוגה מקדימה באמצעות אחד מסוגי הסרטונים שנתמכים על ידי ExoPlayer והתצוגה המקדימה נגישה לציבור, אפשר להפעיל את התצוגה המקדימה ישירות במסך הבית.

כשיוצרים PreviewProgram, משתמשים ב-setPreviewVideoUri() עם כתובת URL מסוג HTTPS שנגישה לכולם, כמו בדוגמה הבאה. התצוגה המקדימה יכולה להיות סרטון או אודיו.

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

איך מרנדרים את התצוגה המקדימה על משטח

אם הסרטון מוגן באמצעות DRM או שהוא מסוג מדיה שלא נתמך על ידי ExoPlayer, צריך להשתמש ב-TvInputService. מסך הבית של Android TV מעביר Surface לשירות שלכם על ידי קריאה ל-onSetSurface(). האפליקציה שלך מציירת סרטון ישירות על פני השטח האלה מ-onTune().

האפשרות 'רינדור ישיר של משטחים' מאפשרת לאפליקציה לשלוט במה שמרונדר ובאופן הרינדור. אפשר להוסיף שכבת מטא-נתונים כמו שיוך לערוץ.

הצהרה על TvInputService במניפסט

האפליקציה צריכה לספק הטמעה של TvInputService כדי שמסך הבית יוכל לעבד את התצוגה המקדימה.

בהצהרת השירות, צריך לכלול מסנן Intent שמציין את TvInputService כפעולה לביצוע באמצעות ה-Intent. צריך גם להצהיר על מטא-נתוני השירות כמשאב XML נפרד. בדוגמה הבאה מוצגים הצהרת השירות, מסנן הכוונות והצהרת המטא-נתונים של השירות:

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

מגדירים את המטא-נתונים של השירות בקובץ XML נפרד. קובץ המטא-נתונים של השירות נמצא בספריית משאבי ה-XML של האפליקציה, והשם שלו חייב להיות זהה לשם המשאב שהצהרתם עליו במניפסט. בעזרת רשומות המניפסט מהדוגמה הקודמת, יוצרים קובץ XML בנתיב res/xml/previewinputservice.xml עם תג tv-input ריק:

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

TV Input Framework חייב לכלול את התג הזה. עם זאת, הוא משמש רק להגדרת ערוצים בשידור חי. מכיוון שאתם מעבדים סרטון, התג צריך להיות ריק.

יצירת URI של סרטון

כדי לציין שסרטון התצוגה המקדימה צריך להיות מוצג על ידי האפליקציה ולא על ידי מסך הבית של Android TV, צריך ליצור URI של סרטון עבור PreviewProgram. כתובת ה-URI צריכה להסתיים במזהה שהאפליקציה משתמשת בו לתוכן, כדי שתוכלו לאחזר את התוכן מאוחר יותר ב-TvInputService.

אם המזהה הוא מסוג Long, משתמשים ב-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();

אם המזהה שלכם הוא לא מסוג Long, צריך ליצור את ה-URI באמצעות 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();

האפליקציה שלך קוראת ל-onTune(Uri videoUri) כדי לגרום ל-Android TV להתחיל את סרטון התצוגה המקדימה.

יצירת שירות

בדוגמה הבאה אפשר לראות איך מרחיבים את TvInputService כדי ליצור PreviewInputService משלכם. שימו לב שהשירות משתמש ב-MediaPlayer להפעלה, אבל הקוד שלכם יכול להשתמש בכל נגן וידאו זמין.

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