开发者控制台

电视直播资源


电视直播资源

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

将程序包添加到允许列表

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

重要须知

Fire TV上的线性电视集成目前仅限指定合作伙伴使用。我们希望在不久的将来能够向所有开发者开放此功能。

最佳实践

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

  • 提供无冲突注册,鼓励在适用情况下试用。例如简化应用上的注册表单或使用电话号码进行注册。
  • 对频道阵容中的每个TvContract.Channels.Logo使用透明的单色标志。
  • 优化深层链接流,以在2.5秒内开始全屏播放。
  • 操作多个频道时,使用批量操作。
  • 在适用情况下使用Gracenote频道ID,以简化集成。
  • 注重元数据加载性能优化,而不是提供完整节目单或偏爱的图像大小。
  • 使用JobSchedulerWorkManager定期检查并确保授权始终准确。即使您的应用未在前台运行,这也可以确保浏览和搜索中的频道与实际的授权频道始终同步。
  • 除了自定义频道排序的情况之外,在授权频道列表稍有变化时,最佳办法是更新现有的授权频道列表,而不是删除并重新添加所有频道。
  • COLUMN_DISPLAY_NAME列提供可显示的频道名称,因为这在UI中可用作回退。Fire TV最多可显示16个字母数字字符或8-10个全角字符。

代码示例

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

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

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

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

/**
 * Uri for deep link of playback into external player.
 * Null or invalid data will result in default as integrated with Fire TV Native Player
 */
private final static String PLAYBACK_DEEP_LINK_URI = "playbackDeepLinkUri";

// The Id for Gracenote input type
private final static String GRACENOTE_ID = "gracenote_ontv"; // gracenote ontv id
private final static String GRACENOTE_GVD = "gracenote_gvd"; // gracenote gvd id

// Contract for playback deep link uri
// Use Intent.URI_INTENT_SCHEME to create uri from intent and to covert back to original intent
Intent playbackDeepLinkIntent = new Intent(); // Created by your app
String playbackDeepLinkUri = playbackDeepLinkIntent.toUri(Intent.URI_INTENT_SCHEME);

// Construct BLOB
ContentValues values = new ContentValues();  // store all the channel data
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#") // replace with GRACENOTE_XXX
                  .put(EXTERNAL_ID_VALUE, "#Actual Id Value#") // replace with gracenote ID value associated with channel
                  .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);

执行家长监护

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

private TvContentRating mBlockedRating = null;

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

    @Override
    public void onUnblockContent(final TvContentRating unblockedRating) {
        // 用户成功输入PIN以解禁
        // 所选评级对应的内容
        if (unblockedRating.unblockContent(mBlockedRating)) {
            // 播放应开始
            notifyContentAllowed();
        }
    }

提供应用横幅

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

// 在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);
}

示例电视直播应用

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

直播应用的区域设置支持

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

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

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

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

  2. 通过ADB连接到Fire TV

    如果您已开启调试并安装了ADB,只需从“Settings(设置)>“设备和软件Device & Software (or 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设备提供的电视直播频道的代码。

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

    电视输入示例
    电视输入示例

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

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

    这将加载示例频道。

    同步源
    同步源
  6. 同步完成后,单击主页按钮。频道现在应该会在“正在播放”行和指南中显示。

    这是“正在播放”行:

    Fire TV“正在播放”行
    Fire TV“正在播放”行

    这是频道指南。

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

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

自定义日志调试

如何查找所有现有频道

以下示例演示了如何在Android电视数据库中查询所有现有频道。

private static String[] CHANNEL_TABLE_PROJECTIONS = new String[] {
    TvContractCompat.Channels._ID,
        TvContractCompat.Channels.COLUMN_DESCRIPTION,
        TvContractCompat.Channels.COLUMN_DISPLAY_NAME,
        TvContractCompat.Channels.COLUMN_DISPLAY_NUMBER,
        TvContractCompat.Channels.COLUMN_INPUT_ID,
        TvContractCompat.Channels.COLUMN_INTERNAL_PROVIDER_DATA,
        TvContractCompat.Channels.COLUMN_NETWORK_AFFILIATION,
        TvContractCompat.Channels.COLUMN_ORIGINAL_NETWORK_ID,
        TvContractCompat.Channels.COLUMN_PACKAGE_NAME,
        TvContractCompat.Channels.COLUMN_SEARCHABLE,
        TvContractCompat.Channels.COLUMN_SERVICE_ID,
        TvContractCompat.Channels.COLUMN_SERVICE_TYPE,
        TvContractCompat.Channels.COLUMN_TRANSPORT_STREAM_ID,
        TvContractCompat.Channels.COLUMN_TYPE,
        TvContractCompat.Channels.COLUMN_VIDEO_FORMAT,
        TvContractCompat.Channels.COLUMN_BROWSABLE,
        TvContractCompat.Channels.COLUMN_LOCKED,
        TvContractCompat.Channels.COLUMN_APP_LINK_COLOR,
        TvContractCompat.Channels.COLUMN_APP_LINK_ICON_URI,
        TvContractCompat.Channels.COLUMN_APP_LINK_INTENT_URI,
        TvContractCompat.Channels.COLUMN_APP_LINK_POSTER_ART_URI,
        TvContractCompat.Channels.COLUMN_APP_LINK_TEXT,
        TvContractCompat.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1,
        TvContractCompat.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2,
        TvContractCompat.Channels.COLUMN_INTERNAL_PROVIDER_FLAG3,
        TvContractCompat.Channels.COLUMN_INTERNAL_PROVIDER_FLAG4
};


public List < Channel > getChannels(ContentResolver resolver) {
    Log.d(Utils.DEBUG_TAG, "Start testing query channel table for preview channels...");
    Cursor cursor = null;
    List < Channel > channels = new ArrayList < > ();
    try {
        cursor = resolver.query(TvContractCompat.Channels.CONTENT_URI, CHANNEL_TABLE_PROJECTIONS, null, null, null);
        if (cursor == null || cursor.getCount() == 0) {
            Log.d(TAG, "No channel inserted \n");
            return null;
        }
        while (cursor.moveToNext()) {
            Channel channel = Channel.fromCursor(cursor);
            channels.add(channel);
            Log.d(TAG, "Found channel " + channel);
        };
    } catch (Exception e) {
        Log.d(TAG, "Unable to get the channels " + e);
        return null;
    } finally {
        if (cursor != null) {
            cursor.close();

        }
    }
    return channels;
}

如何查找某个频道中所有的节目

以下是现有节目的查询方法。

代码示例: 查询属于特定频道的节目元数据的示例:

public static List < Program > getPrograms(ContentResolver resolver, Uri channelUri) {
    if (channelUri == null) {
        return null;
    }
    Uri uri = TvContract.buildProgramsUriForChannel(channelUri);
    List < Program > programs = new ArrayList < > ();
    // TvProvider在默认情况下按时间顺序返回节目。
    Cursor cursor = null;
    try {
        cursor = resolver.query(uri, Program.PROJECTION, null, null, null);
        if (cursor == null || cursor.getCount() == 0) {
            return programs;
        }
        while (cursor.moveToNext()) {
            programs.add(Program.fromCursor(cursor));
        }
    } catch (Exception e) {
        Log.w(TAG, "Unable to get programs for " + channelUri, e);
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return programs;
}