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

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

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

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

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

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

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

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 כפעולה שיש לבצע עם בכוונה טובה. בנוסף, מצהירים על המטא-נתונים של השירות כמשאב XML נפרד. מוצגים הצהרת שירות, מסנן Intent והצהרת מטא-נתונים של שירות בדוגמה הבאה:

<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 קלט 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/
            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
        }
    }
}