开发者控制台

直播TV资源

直播TV资源

以下最佳实践、代码示例和其他参考将帮助您更好地了解直播集成在实现阶段的细节。

将程序包添加到允许列表

允许列表确定了哪些应用能够在Fire TV浏览和搜索体验中显示其频道。

最佳实践

以下产品和实施指南将为您的客户提供最佳的Fire TV电视直播体验:

  • 提供无冲突注册,鼓励在适用情况下试用。例如简化应用上的注册表单或使用电话号码进行注册。
  • 对频道阵容中的每个TvContract.Channels.Logo使用透明的单色标志。
  • 优化深层链接流,以在2.5秒内开始全屏播放。
  • 操作多个频道时,使用批量操作。
  • 在适用情况下使用Gracenote频道ID,以简化集成。
  • 注重元数据加载性能优化,而不是提供完整节目单或偏爱的图像大小。
  • 使用JobSchedulerWorkManager定期检查并确保授权始终准确。即使您的应用未在前台运行,这也可以确保浏览和搜索中的频道与实际的授权频道始终同步。
  • 除了自定义频道排序的情况之外,在授权频道列表稍有变化时,最佳办法是更新现有的授权频道列表,而不是删除并重新添加所有频道。
  • COLUMN_DISPLAY_NAME中提供将在Fire TV UI中显示的频道显示名称。Fire TV最多显示25个字母数字字符,但如果长度超过此限制,则不会显示完整的频道名称。此最大数目限制适用于半角和全角字符集。以下为是否能显示的示例: The Walking Dead Universe(最大长度-通过)/简短名称(通过)/特别长的站名(失败)/韓流・華流韓流・華流韓(最大长度-通过)。
  • 每次执行修改之前,请先查询TIF数据库,确定数据库内已有哪些频道。
  • 插入频道之前,请确保该频道尚不存在。如果该频道已存在,请检查确认元数据是不是最新的。仅当元数据需要更新时,才应该执行数据库更新操作。
  • 应检查确认数据库游标为空。如果游标为空,请针对所有带有输入ID的频道发送删除请求,然后重新插入频道。

代码示例

本节包含与直播TV集成相关的代码示例。

TVContractUtils中的以下代码显示了如何将Gracenote ID和深层链接添加到电视数据库中。

/**
 * 用于存储外部ID类型的变量,用于匹配的服务元数据。有效类型为
 * 下面定义为带有前缀“EXTERNAL_ID_TYPE_”的常量
 * 空或无效数据将导致
 * 元数据的服务匹配失败
 */
private final static String EXTERNAL_ID_TYPE = "externalIdType";

/**
 * 用于存储外部ID的值的变量,用于匹配的服务元数据。
 * 空值或无效数据将导致元数据的服务匹配失败
 */
private final static String EXTERNAL_ID_VALUE = "externalIdValue";

/**
 * 用于在外部播放器中插入播放的深层链接的URI。
 * 空或无效数据将导致默认与Fire TV原生播放器集成
 */
private final static String PLAYBACK_DEEP_LINK_URI = "playbackDeepLinkUri";

// Gracenote输入类型的ID
private final static String GRACENOTE_ID = "gracenote_ontv"; // gracenote ontv id
private final static String GRACENOTE_GVD = "gracenote_gvd"; // gracenote gvd id

// 播放深层链接URI的合约
// 使用Intent.URI_INTENT_SCHEME从意图创建URI并转换回原始意图
Intent playbackDeepLinkIntent = new Intent(); // 由您的应用创建
String playbackDeepLinkUri = playbackDeepLinkIntent.toUri(Intent.URI_INTENT_SCHEME);

// 构建BLOB
ContentValues values = new ContentValues();  // 存储所有频道数据
ContentResolver resolver = context.getContentResolver();
values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, "#Actual display name#");
values.put(TvContract.Channels.COLUMN_INPUT_ID, "#Actual input id#");
try {
    String jsonString = new JSONObject()
                  .put(EXTERNAL_ID_TYPE, "#Actual Id Type#") // 替换为GRACENOTE_XXX
                  .put(EXTERNAL_ID_VALUE, "#Actual Id Value#") // 替换为与频道关联的gracenote ID值
                  .put(PLAYBACK_DEEP_LINK_URI, playbackDeepLinkUri).toString();

    values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, jsonString.getBytes());
} catch (JSONException e) {
    Log.e(TAG, "Error when adding data to blob " + e);
}

Uri uri = resolver.insert(TvContract.Channels.CONTENT_URI, values);
import android.app.Activity
import android.content.ContentValues
import android.content.Intent
import android.media.tv.TvContract
import android.util.Log
import org.json.JSONException
import org.json.JSONObject

/**
 * 用于存储外部ID类型的变量,用于匹配的服务元数据。有效
 * 类型在下面定义为前缀为"EXTERNAL_ID_TYPE_"的常量空值或无效数据将
 * 导致元数据服务匹配失败
 */
private const val EXTERNAL_ID_TYPE = "externalIdType"
/**
 * 用于存储外部ID的值的变量,用于匹配的服务元数据。空值
 *或无效数据将导致元数据的服务匹配失败
 */
private const val EXTERNAL_ID_VALUE = "externalIdValue"
/**
 * 用于在外部播放器中插入播放的深层链接的URI。空值或无效数据将导致默认
 * 与Fire TV原生播放器集成
 */
private const val PLAYBACK_DEEP_LINK_URI = "playbackDeepLinkUri"
// Gracenote输入类型的ID
private const val GRACENOTE_ID = "gracenote_ontv" // gracenote ontv id
private const val GRACENOTE_GVD = "gracenote_gvd" // gracenote gvd id



class SetupActivity : Activity() {
    private fun insertChannel(): Long? {
        // 播放深层链接URI的合约
        // 使用Intent.URI_INTENT_SCHEME从意图创建URI并转换回原始意图
        val playbackDeepLinkIntent = Intent() // 由您的应用创建
        val playbackDeepLinkUri = playbackDeepLinkIntent.toUri(Intent.URI_INTENT_SCHEME)

        val jsonString: String? = try {
             JSONObject()
                .put(EXTERNAL_ID_TYPE, "#Actual Id Type#") // 替换为GRACENOTE_XXX
                .put(
                    EXTERNAL_ID_VALUE,
                    "#Actual Id Value#"
                ) // 替换为与频道关联的gracenote ID值
                .put(PLAYBACK_DEEP_LINK_URI, playbackDeepLinkUri)
                .toString()
        } catch (e: JSONException) {
            Log.e(TAG, "Error when adding data to blob", e)
            null
        }

        // 构建BLOB
        val values = ContentValues().apply { //存储所有频道数据
            put(TvContract.Channels.COLUMN_DISPLAY_NAME, "#Actual display name#")
            put(TvContract.Channels.COLUMN_INPUT_ID, "#Actual input id#")
            if (jsonString != null) {
                put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, jsonString.toByteArray())
            }
        }

        val uri = contentResolver.insert(TvContract.Channels.CONTENT_URI, values)

        Log.i("SetupActivity", "Channel Inserted! Uri:$uri")
        return uri?.lastPathSegment?.toLongOrNull()
    }
}

private val TAG = "MyTAG"

执行家长监护

以下代码演示了如何侦听针对实时预览播放的家长监护。

private TvContentRating mBlockedRating = null;

    @Override
    public boolean onTune(final Uri channelUri) {
        ...
        if (mTvInputManager.isParentalControlsEnabled()) {
            // 确保播放在Surface上无法听到或看见
            mBlockedRating = <content_rating>;
            // 1.在全屏播放时为用户触发PIN提示
            // 2.确保浏览On Now行时节目画面
            // 不会跳转到播放Surface。
            notifyContentBlocked(mBlockedRating);
        } else {
            // 播放应开始
            notifyContentAllowed();
        }
        ...
    }

    @Override
    public void onUnblockContent(final TvContentRating unblockedRating) {
        // 用户成功输入PIN以解禁
        // 所选评级对应的内容
        if (unblockedRating.unblockContent(mBlockedRating)) {
            // 播放应开始
            notifyContentAllowed();
        }
    }
import android.content.Context
import android.media.tv.TvContentRating
import android.media.tv.TvInputManager
import android.media.tv.TvInputService
import android.net.Uri
import android.view.Surface

private const val TAG = "MyTag"

private class PreviewSession(context: Context) :
    TvInputService.Session(context) {

    private val tvInputManager: TvInputManager = TODO()



    override fun onTune(channelUri: Uri): Boolean {
        if (tvInputManager.isParentalControlsEnabled) {
            // 确保在Surface上无法听到或看到播放
            val blockedRating = getContentRating(channelUri)
            // 1.在全屏播放时为用户触发PIN提示
            // 2.确保浏览On Now行时节目画面
            // 不会跳转到播放Surface。
            notifyContentBlocked(blockedRating)
        } else {
            // 播放应开始
            notifyContentAllowed()
        }
        return true
    }

    override fun onUnblockContent(unblockedRating: TvContentRating) {
        // 用户成功输入PIN以解禁
        // 适用于给定评级的内容
        if (unblockedRating.unblockContent(mBlockedRating)) { // <-- 这是什么?
            // 播放应开始
            notifyContentAllowed();
        }
    }
}

private fun getContentRating(channelUri: Uri): TvContentRating = TODO()

提供应用横幅

要在直播TV设置中显示应用横幅,须通过程序包管理器提供应用横幅。

// 在AndroidManifest.xml中
<application
    android:allowBackup="false"
    android:label="@string/app_name"
    android:banner="@drawable/app_icon_banner"
    tools:replace="android:allowBackup, allow:label, android:theme" >

    <meta-data
        android:name="****"
        android:value="true"
    />
</application>

要测试横幅,请参阅以下代码片段:

Drawable appDrawable = null;
try {
    String packageName = "****"; // replace **** with real package name
    PackageManager packageManager = getContext().getPackageManager();
    appDrawable = packageManager.getApplicationBanner(packageName);
} catch (PackageManager.NameNotFoundException e) {
    Log.i(TAG, "Can't find application banner for package : " + packageName);
}
val packageName = "****" //将****替换为程序包名称
val appDrawable: Drawable? = try {
    packageManager.getApplicationBanner(packageName)
} catch (e: PackageManager.NameNotFoundException) {
    Log.i("SetupActivity", "Can't find application banner for package : $packageName")
    null
}

示例直播TV应用

GitHub在github.com/amzn/ftv-livetv-sample-tv-app提供了一个具有直播TV集成的示例应用。这个示例电视应用是基于Google示例电视应用。您可以使用此示例应用作为Fire TV电视直播集成的参考。

直播应用的区域设置支持

仅以下区域设置支持示例应用: 美国、加拿大、英国、德国、日本、西班牙和印度。其他市场即将推出支持。

要加载示例应用,请执行以下操作:

  1. 转到https://github.com/amzn/ftv-livetv-sample-tv-app,单击Clone or download(克隆或下载),然后单击Download ZIP(下载ZIP)。解压下载文件。

    该应用显示了集成直播TV的示例代码。要查看结果,请使用ADB将app-debug.apk文件侧载到您的Fire TV上,如以下步骤所述。

  2. 通过ADB连接到Fire TV

    如果您已开启调试并安装了ADB,只需前往Settings(设置)> Device & Software (或My Fire TV)(设备和软件,或“我的Fire TV”)> About(关于)> Network(网络)并获取Fire TV的IP地址,然后运行以下内容,从而自定义您自己Fire TV的IP地址:

    adb connect 123.456.7.89:5555
    

    123.456.7.89替换为您的Fire TV的IP地址。(如果您在连接时遇到问题,并且您正在使用公司VPN,请尝试断开VPN连接,因为您的计算机需要与您的Fire TV处于同一个WiFi网络中。)

  3. 在示例应用中安装构建的APK:

    adb install -r AndroidTvSampleInput/app/build/outputs/apk/app-debug.apk
    

    响应如下:

    Performing Streamed Install
    
    Success
    

    请注意,此示例应用不会作为传统意义上的独立应用启动。相反,该应用包括了Fire TV设备提供的直播TV频道的代码。

  4. 在您的Fire TV设备上,前往Settings > Applications(应用)> Manage Installed Applications(管理已安装应用)。选择Sample TV Inputs(电视输入示例)。然后单击启动应用

    电视输入示例
    Sample TV inputs

    这将带您进入亚马逊开发者门户。

    亚马逊Fire TV网站
    亚马逊Fire TV网站
  5. 在您的Fire TV遥控器上,单击Home(主页)按钮以退出此界面。然后前往Settings > Live TV(直播TV)> Synch Sources(同步源)> Amazon Sample TV Input(亚马逊电视输入示例)。

    这将加载示例频道。

    同步源
    同步源
  6. 同步完成后,单击Home按钮。频道现在应该会在On Now(当前热映)行和指南中显示。

    这是On Now行。

    Fire TV On Now行
    Fire TV On Now行

    这是Channel Guide(频道指南)。

    Fire TV频道指南
    Fire TV频道指南

    要导航到Fire TV上的频道指南,请转到主屏幕,向下滚动到On Now行,按遥控器上的Menu(菜单)按钮,然后点Channel Guide。您也可以按一下遥控器上的麦克风按钮,然后说“Channel Guide”。


Last updated: 2024年6月18日