Xem trước video

Video xem trước là một cách hiệu quả để khuyến khích người dùng liên kết sâu vào ứng dụng truyền hình của bạn. Video xem trước có thể là đoạn video ngắn hoặc đoạn giới thiệu đầy đủ về phim.

Khi tạo bản xem trước, hãy cân nhắc những nguyên tắc sau:

  • Không hiển thị quảng cáo trong bản xem trước. Nếu bạn ghép quảng cáo ở phía máy khách, đừng ghép quảng cáo vào video xem trước. Nếu bạn ghép quảng cáo ở phía máy chủ, hãy cung cấp video không có quảng cáo để xem trước.
  • Để có chất lượng tốt nhất, video xem trước nên có tỷ lệ khung hình là 16:9 hoặc 4:3. Hãy xem Thuộc tính chương trình video để biết kích thước được đề xuất cho video xem trước.
  • Khi video xem trước và ảnh áp phích có tỷ lệ khung hình khác nhau, màn hình chính sẽ điều chỉnh kích thước chế độ xem áp phích theo tỷ lệ khung hình của video trước khi phát bản xem trước. Video không có hiệu ứng hòm thư. Ví dụ: nếu tỷ lệ ảnh áp phích là ASPECT_RATIO_MOVIE_POSTER (1:1, 441) nhưng tỷ lệ video là 16:9, thì khung hiển thị áp phích sẽ chuyển đổi thành vùng 16:9.
  • Khi bạn tạo một bản xem trước, nội dung của bản xem trước đó có thể truy cập công khai hoặc được bảo vệ theo DRM. Mỗi trường hợp sẽ áp dụng một quy trình khác nhau. Trang này mô tả cả hai.

Phát bản xem trước trên màn hình chính

Nếu tạo một bản xem trước bằng bất kỳ loại video nào mà ExoPlayer hỗ trợ và bản xem trước đó có thể truy cập công khai, thì bạn có thể phát bản xem trước ngay trên màn hình chính.

Khi bạn tạo một PreviewProgram, hãy sử dụng setPreviewVideoUri() với một URL HTTPS có thể truy cập công khai như trong ví dụ sau. Bản xem trước có thể là video hoặc âm thanh.

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

Hiển thị bản xem trước trên một bề mặt

Nếu video của bạn được bảo vệ bằng DRM hoặc thuộc loại nội dung đa phương tiện mà ExoPlayer không hỗ trợ, hãy sử dụng TvInputService. Màn hình chính của Android TV truyền một Surface đến dịch vụ của bạn bằng cách gọi onSetSurface(). Ứng dụng của bạn vẽ video trực tiếp trên bề mặt này từ onTune().

Tính năng kết xuất trực tiếp trên bề mặt cho phép ứng dụng của bạn kiểm soát nội dung và cách kết xuất. Bạn có thể phủ siêu dữ liệu, chẳng hạn như mô hình phân bổ theo kênh.

Khai báo TvInputService trong tệp kê khai

Ứng dụng của bạn phải triển khai TvInputService để màn hình chính có thể kết xuất bản xem trước.

Trong nội dung khai báo dịch vụ, hãy thêm một bộ lọc ý định chỉ định TvInputService làm thao tác cần thực hiện với ý định. Ngoài ra, hãy khai báo siêu dữ liệu dịch vụ dưới dạng một tài nguyên XML riêng biệt. Phần khai báo dịch vụ, bộ lọc ý định và phần khai báo siêu dữ liệu dịch vụ xuất hiện trong ví dụ sau:

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

Xác định siêu dữ liệu dịch vụ trong một tệp XML riêng biệt. Tệp siêu dữ liệu dịch vụ nằm trong thư mục tài nguyên XML của ứng dụng và phải khớp với tên của tài nguyên mà bạn đã khai báo trong tệp kê khai. Khi sử dụng các mục kê khai trong ví dụ trước, bạn sẽ tạo một tệp XML tại res/xml/previewinputservice.xml, với thẻ tv-input trống:

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

TV Input Framework phải có thẻ này. Tuy nhiên, chế độ này chỉ được dùng để định cấu hình các kênh phát trực tiếp. Vì bạn đang kết xuất một video, nên thẻ này phải trống.

Tạo URI video

Để cho biết rằng ứng dụng của bạn sẽ hiển thị video xem trước thay vì màn hình chính của Android TV, bạn phải tạo một URI video cho PreviewProgram. URI phải kết thúc bằng giá trị nhận dạng mà ứng dụng của bạn dùng cho nội dung, nhờ đó, bạn có thể truy xuất nội dung sau này trong TvInputService.

Nếu giá trị nhận dạng của bạn thuộc loại Long, hãy sử dụng 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();

Nếu giá trị nhận dạng của bạn không thuộc loại Long, hãy tạo URI bằng 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();

Ứng dụng của bạn gọi onTune(Uri videoUri) để Android TV bắt đầu video xem trước.

Tạo một dịch vụ

Ví dụ sau đây cho thấy cách mở rộng TvInputService để tạo PreviewInputService của riêng bạn. Xin lưu ý rằng dịch vụ này sử dụng MediaPlayer để phát, nhưng mã của bạn có thể sử dụng bất kỳ trình phát video nào có sẵn.

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