미리보기 동영상

미리보기 동영상은 사용자가 TV 앱에 딥 링크로 연결하도록 유도하는 좋은 방법입니다. 미리보기는 짧은 클립부터 전체 영화 예고편까지 다양합니다.

미리보기를 만들 때 다음 가이드라인을 고려하세요.

  • 미리보기에 광고를 표시하지 않습니다. 클라이언트 측에서 광고를 연결하면 미리보기 동영상에 병합하지 않습니다. 서버 측에서 광고를 연결하는 경우 광고 없는 동영상 미리보기 제공
  • 최적의 품질을 위해 미리보기 동영상은 16:9 또는 4:3이어야 합니다. 자세한 내용은 동영상 프로그램 속성 미리 볼 수 있습니다
  • 미리보기 동영상과 포스터 아트의 가로세로 비율이 다를 경우 홈 화면에서는 미리보기를 재생하기 전에 포스터 뷰의 크기를 동영상의 가로세로 비율로 조절합니다. 동영상이 레터박스 처리되지 않습니다. 예를 들어 포스터 아트 비율은 ASPECT_RATIO_MOVIE_POSTER (1:1.441) 동영상 비율이 16:9이고 포스터 뷰가 16:9 영역으로 전환됩니다.
  • 미리보기를 만들면 공개적인 콘텐츠에 액세스하거나 DRM에 의하여 보호됩니다. 각 사례에 다른 절차가 적용됩니다. 이 페이지 는 둘 다 설명합니다.

홈 화면에서 미리보기 재생

동영상 유형 중 하나를 사용하여 미리보기를 만드는 경우 ExoPlayer에서 지원 미리보기에 공개적으로 액세스할 수 있다면 홈 화면에서 바로 미리보기를 재생할 수 있습니다.

PreviewProgram 공개적으로 액세스 가능한 HTTPS로 setPreviewVideoUri() 사용 URL을 포함해야 합니다. 미리보기는 다음 중 하나일 수 있습니다. video 또는 오디오.

Kotlin

val previewVideoUrl = Uri.parse("https://www.example.com/preview.mp4")
val builder = PreviewProgram.Builder()
builder.setChannelId(channelId)
    // ...
    .setPreviewVideoUri(previewVideoUrl)

자바

Uri previewVideoUrl = Uri.parse("https://www.example.com/preview.mp4");
PreviewProgram.Builder builder = new PreviewProgram.Builder();
builder.setChannelId(channelId)
    // ...
    .setPreviewVideoUri(Uri.parse(previewVideoUrl));

표면에서 미리보기 렌더링

동영상이 DRM으로 보호되거나 Google에서 지원하지 않는 미디어 유형인 경우 ExoPlayerTvInputService을 사용합니다. Android TV 홈 화면이 Surface를 서비스에 전달합니다. 이를 위해 onSetSurface()를 호출합니다. 앱은 onTune()에서 이 표면에 직접 동영상을 그립니다.

직접 표면 렌더링을 사용하면 앱에서 렌더링 내용과 방식을 제어할 수 있습니다. 있습니다. 채널 저작자 표시와 같은 메타데이터를 오버레이할 수 있습니다.

매니페스트에서 TvInputService 선언

앱에서 TvInputService 구현을 제공해야 합니다. 홈 화면에서 미리보기를 렌더링할 수 있습니다.

서비스 선언에 다음을 지정하는 인텐트 필터를 포함합니다. TvInputService는 인텐트를 지정할 수 있습니다. 또한 서비스 메타데이터를 별도의 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 리소스 디렉터리에 있습니다. 하고 합니다. 이전 예의 매니페스트 항목을 사용하면 res/xml/previewinputservice.xml에 빈 상태로 XML 파일을 만듭니다. tv-input 태그:

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

TV 입력 프레임워크에 이 태그가 있어야 합니다. 하지만 실시간 채널을 구성하는 데만 사용됩니다. 동영상을 렌더링하는 중이므로 태그는 비어 있어야 합니다.

동영상 URI 만들기

미리보기 동영상을 앱이 아닌 앱에서 렌더링해야 함을 나타내기 위해 PreviewProgram의 동영상 URI를 만들어야 합니다. 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()

자바

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

자바

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