미리보기 동영상은 사용자가 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에서 지원하지 않는 미디어 유형인 경우
ExoPlayer는 TvInputService
을 사용합니다.
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 } } }