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 đến ứng dụng TV của bạn. Các bản xem trước có thể từ các đoạn video ngắn cho đến các đoạn giới thiệu phim đầy đủ.

Khi bạn tạo bản xem trước, hãy cân nhắc các 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 chèn quảng cáo ở phía máy chủ, hãy cung cấp một 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ệ 16:9 hoặc 4:3. Xem Thuộc tính chương trình video để biết các kích thước đề xuất của 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 kích thước chế độ xem áp phích thành tỷ lệ khung hình của video trước khi phát bản xem trước. Video không có khung viền 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 tỷ lệ khung hình 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ể được truy cập công khai hoặc được bảo vệ bằng DRM. Mỗi trường hợp sẽ có quy trình riêng. Trang này mô tả cả hai.

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

Nếu bạn tạo một bản xem trước bằng bất kỳ loại video nào được 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 loại HTTPS có thể truy cập công khai như trong ví dụ bên dưới. 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));

Kết xuất bản xem trước trên một nền tảng

Nếu video của bạn được bảo vệ bằng DRM hoặc trong một 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 chuyển 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 ngay trên nền tảng này qua onTune().

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

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

Ứng dụng của bạn phải cung cấp phương thức triển khai TvInputService để màn hình chính có thể hiển thị bản xem trước.

Trong phần khai báo dịch vụ, hãy thêm một bộ lọc ý định chỉ định TvInputService làm hành động cần thực hiện với ý định. Đồng thời, 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. Phần khai báo dịch vụ, bộ lọc ý định và nội dung khai báo siêu dữ liệu dịch vụ được thể 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>

Hãy xác định siêu dữ liệu dịch vụ trong một tệp XML riêng. 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 bạn đã khai báo trong tệp kê khai. Sử dụng các mục kê khai từ ví dụ trước, bạn sẽ tạo tệp XML tại res/xml/previewinputservice.xml, có một thẻ tv-input trống:

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

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

Tạo URI video

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

Nếu giá trị nhận dạng là 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 phải là loại Long, hãy tạo URI bằng cách sử dụ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 hàm onTune(Uri videoUri) để yêu cầu Android TV bắt đầu xem video xem trước.

Tạo một dịch vụ

Ví dụ sau đây cho biết cách mở rộng TvInputService để tạo PreviewInputService của riêng bạn. Xin lưu ý rằng dịch vụ sử dụng MediaPlayer để phát, nhưng mã của bạn có thể dùng mọi trình phát video 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/
            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
        }
    }
}