Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

미리보기 동영상

미리보기 동영상을 통해 사용자에게 TV 앱에 딥 링크로 연결하도록 효과적으로 장려할 수 있습니다. 미리보기는 짧은 클립부터 전체 동영상 예고편까지 다양합니다.

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

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

홈 화면에서 미리보기 재생

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

PreviewProgram을 빌드할 때 아래 예와 같이 공개적으로 액세스 가능한 HTTPS URL과 함께 setPreviewVideoUri()를 사용합니다. 미리보기는 동영상이나 오디오일 수 있습니다.

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에 따라 보호되거나 ExoPlayer에서 지원되지 않는 미디어 유형이라면 TvInputService를 사용합니다. Android TV 홈 화면은 onSetSurface()를 호출하여 Surface를 서비스에 전달합니다. 앱은 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에 빈 tv-input 태그가 포함된 XML 파일을 만듭니다.

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

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

동영상 URI 만들기

Android TV 홈 화면이 아니라 앱에서 미리보기 동영상을 렌더링해야 한다는 것을 나타내려면 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.withAppendedPath()를 사용하여 URI를 빌드합니다.

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

자바

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