Видеоанонсы – отличный способ побудить пользователей перейти по ссылке на контент в ваше ТВ-приложение. Это могут быть как короткие клипы, так и полноценные трейлеры фильмов.
При создании предварительной версии учитывайте следующие рекомендации:
- Не показывайте рекламу в предварительном просмотре. Если вы вставляете объявления на стороне клиента, не добавляйте их в предварительные версии видео. Если вы вставляете рекламу на стороне сервера, предоставьте для предварительного просмотра видео без рекламы.
- Для наилучшего качества видео для предварительного просмотра должны иметь соотношение сторон 16:9 или 4:3. Рекомендуемые размеры тизеров указаны в разделе Атрибуты видеопрограмм.
- Если соотношение сторон у предпросмотра видео и постера отличается, то на главном экране перед воспроизведением предпросмотра постер будет приведен к соотношению сторон видео. Видео не леттербокс. Например, если соотношение сторон постера –
ASPECT_RATIO_MOVIE_POSTER(1:1,441), а видео – 16:9, то постер будет преобразован в формат 16:9. - При создании предварительной версии ее контент может быть общедоступным или защищенным DRM. В каждом случае применяются разные процедуры. В этом разделе рассматривается несколько возможностей.
Воспроизвести тизер на главном экране
Если вы создадите предварительный просмотр, используя любой из типов видео, поддерживаемых ExoPlayer, и он будет общедоступным, то его можно будет воспроизвести прямо на главном экране.
При создании PreviewProgram используйте setPreviewVideoUri() с общедоступным URL по HTTPS, как показано в примере ниже. Предварительный просмотр может быть в формате видео или аудио.
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));
Отрисовка предварительного просмотра на поверхности
Если ваше видео защищено технологиями DRM или имеет тип медиаконтента, не поддерживаемый 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 вашего приложения и должен соответствовать названию ресурса, объявленного в манифесте. Используя записи манифеста из предыдущего примера, вы создадите XML-файл в res/xml/previewinputservice.xml с пустым тегом tv-input:
<?xml version="1.0" encoding="utf-8"?>
<tv-input/>
Этот тег должен быть у TV Input Framework. Однако он используется только для настройки телеканалов. Поскольку вы отрисовываете видео, тег должен быть пустым.
Создайте URI видео
Чтобы указать, что видео предварительного просмотра должно отрисовываться вашим приложением, а не на главном экране Android TV, необходимо создать URI видео для PreviewProgram.
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()
Java
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()
Java
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/<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
}
}
}