为您的 Android 应用添加高级功能

广告插播时间点

Android Sender SDK 支持在给定的媒体流中添加广告插播和随播广告。

如需详细了解广告插播的工作原理,请参阅 Web 接收器广告插播概览。

虽然可以在发送者和接收者上指定插播广告,但建议在 Web 接收者和 Android TV 接收者上指定插播广告,以确保跨平台行为的一致性。

在 Android 上,使用 AdBreakClipInfo 和 AdBreakInfo 在加载命令中指定广告插播时间点:

Kotlin
val breakClip1: AdBreakClipInfo =
    AdBreakClipInfo.Builder("bc0")
        .setTitle("Clip title")
        .setPosterUrl("https://www.some.url")
        .setDuration(60000)
        .setWhenSkippableInMs(5000)  // Set this field so that the ad is skippable
        .build()

val breakClip2: AdBreakClipInfo = …
val breakClip3: AdBreakClipInfo = …

val break1: AdBreakClipInfo =
    AdBreakInfo.Builder(/* playbackPositionInMs= */ 10000)
        .setId("b0")
        .setBreakClipIds({"bc0","bc1","bc2"})
        …
        .build()

val mediaInfo: MediaInfo = MediaInfo.Builder()
    …
    .setAdBreaks({break1})
    .setAdBreakClips({breakClip1, breakClip2, breakClip3})
    .build()

val mediaLoadRequestData: MediaLoadRequestData = MediaInfo.Builder()
    …
    .setMediaInfo(mediaInfo)
    .build()

remoteMediaClient.load(mediaLoadRequestData)
Java
AdBreakClipInfo breakClip1 =
    new AdBreakClipInfo.Builder("bc0")
        .setTitle("Clip title")
        .setPosterUrl("https://www.some.url")
        .setDuration(60000)
        .setWhenSkippableInMs(5000)  // Set this field so that the ad is skippable
        .build();

AdBreakClipInfo breakClip2 = …
AdBreakClipInfo breakClip3 = …

AdBreakInfo break1 =
    new AdBreakInfo.Builder(/* playbackPositionInMs= */ 10000)
        .setId("b0")
        .setBreakClipIds({"bc0","bc1","bc2"})
        …
        .build();

MediaInfo mediaInfo = new MediaInfo.Builder()
    …
    .setAdBreaks({break1})
    .setAdBreakClips({breakClip1, breakClip2, breakClip3})
    .build();

MediaLoadRequestData mediaLoadRequestData = new MediaInfo.Builder()
    …
    .setMediaInfo(mediaInfo)
    .build();

remoteMediaClient.load(mediaLoadRequestData);

添加自定义操作

发送方应用可以扩展 MediaIntentReceiver 来处理自定义操作或替换其行为。如果您已实现自己的 MediaIntentReceiver,则需要将其添加到清单中,并在 CastMediaOptions 中设置其名称。此示例提供了自定义操作,用于替换切换远程媒体播放、按媒体按钮和其他类型的操作。

// In AndroidManifest.xml
<receiver android:name="com.example.MyMediaIntentReceiver" />
Kotlin
// In your OptionsProvider
var mediaOptions = CastMediaOptions.Builder()
    .setMediaIntentReceiverClassName(MyMediaIntentReceiver::class.java.name)
    .build()

// Implementation of MyMediaIntentReceiver
internal class MyMediaIntentReceiver : MediaIntentReceiver() {
    override fun onReceiveActionTogglePlayback(currentSession: Session) {
    }

    override fun onReceiveActionMediaButton(currentSession: Session, intent: Intent) {
    }

    override fun onReceiveOtherAction(context: Context?, action: String, intent: Intent) {
    }
}
Java
// In your OptionsProvider
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setMediaIntentReceiverClassName(MyMediaIntentReceiver.class.getName())
        .build();

// Implementation of MyMediaIntentReceiver
class MyMediaIntentReceiver extends MediaIntentReceiver {
    @Override
    protected void onReceiveActionTogglePlayback(Session currentSession) {
    }

    @Override
    protected void onReceiveActionMediaButton(Session currentSession, Intent intent) {
    }

    @Override
    protected void onReceiveOtherAction(Context context, String action, Intent intent) {
    }
}

添加自定义渠道

为了让发送方应用与接收方应用进行通信,您的应用需要创建自定义渠道。发送方可以使用自定义渠道向接收方发送字符串消息。每个自定义渠道都由唯一的命名空间定义,并且必须以 urn:x-cast: 前缀开头,例如 urn:x-cast:com.example.custom。您可以拥有多个自定义渠道,每个渠道都有唯一的命名空间。接收方应用还可以使用同一命名空间发送和接收消息。

自定义渠道通过 Cast.MessageReceivedCallback 接口实现:

Kotlin
class HelloWorldChannel : MessageReceivedCallback {
    val namespace: String
        get() = "urn:x-cast:com.example.custom"

    override fun onMessageReceived(castDevice: CastDevice, namespace: String, message: String) {
        Log.d(TAG, "onMessageReceived: $message")
    }
}
Java
class HelloWorldChannel implements Cast.MessageReceivedCallback {
    public String getNamespace() {
        return "urn:x-cast:com.example.custom";
    }
    @Override
    public void onMessageReceived(CastDevice castDevice, String namespace, String message) {
        Log.d(TAG, "onMessageReceived: " + message);
    }
}

发送方应用连接到接收方应用后,可以使用 setMessageReceivedCallbacks 方法创建自定义渠道:

Kotlin
try {
    mCastSession.setMessageReceivedCallbacks(
        mHelloWorldChannel.namespace,
        mHelloWorldChannel)
} catch (e: IOException) {
    Log.e(TAG, "Exception while creating channel", e)
}
Java
try {
    mCastSession.setMessageReceivedCallbacks(
            mHelloWorldChannel.getNamespace(),
            mHelloWorldChannel);
} catch (IOException e) {
    Log.e(TAG, "Exception while creating channel", e);
}

创建自定义渠道后,发送方可以使用 sendMessage 方法通过该渠道向接收方发送字符串消息:

Kotlin
private fun sendMessage(message: String) {
    if (mHelloWorldChannel != null) {
        try {
            mCastSession.sendMessage(mHelloWorldChannel.namespace, message)
                .setResultCallback { status ->
                    if (!status.isSuccess) {
                        Log.e(TAG, "Sending message failed")
                    }
                }
        } catch (e: Exception) {
            Log.e(TAG, "Exception while sending message", e)
        }
    }
}
Java
private void sendMessage(String message) {
    if (mHelloWorldChannel != null) {
        try {
            mCastSession.sendMessage(mHelloWorldChannel.getNamespace(), message)
                .setResultCallback( status -> {
                    if (!status.isSuccess()) {
                        Log.e(TAG, "Sending message failed");
                    }
                });
        } catch (Exception e) {
            Log.e(TAG, "Exception while sending message", e);
        }
    }
}

支持自动播放

请参阅自动播放和排队 API 部分。

替换用户体验 widget 的图片选择

框架的各个组件(即 Cast 对话框、迷你控制器和 UIMediaController [如果已配置])将显示当前正在投放的媒体的封面。图片资源的网址通常包含在媒体的 MediaMetadata 中,但发送方应用可能具有网址的替代来源。

ImagePicker 类定义了一种方法,用于根据图片用途(例如通知缩略图或全屏背景)从 MediaMetadata 中的图片列表中选择合适的图片。默认的 ImagePicker 实现始终选择第一张图片,如果 MediaMetadata 中没有图片,则返回 null。您的应用可以对 ImagePicker 进行子类化并替换 onPickImage(MediaMetadata, ImageHints) 方法以提供替代实现,然后使用 CastMediaOptions.Builder 的 setImagePicker 方法选择该子类。ImageHints 向 ImagePicker 提供有关要在界面中显示的图片的类型和大小的提示。

自定义 Cast 对话框

管理会话生命周期

SessionManager 是管理会话生命周期的中心位置。SessionManager 监听 Android MediaRouter 路由选择状态变化,以开始、恢复和结束会话。选择路线后,SessionManager 会创建一个 Session 对象,并尝试启动或恢复该对象。当路线未被选中时,SessionManager 将结束当前会话。

因此,为确保 SessionManager 能够正确管理会话生命周期,您必须确保:

根据您创建投屏对话框的方式,可能需要执行其他操作:

零设备状态

如果您创建自定义 Cast 对话框,则自定义 MediaRouteChooserDialog 应正确处理未找到任何设备的情况。对话框应包含指示器,以便用户清楚了解应用何时仍在尝试查找设备,以及何时不再尝试查找。

如果您使用的是默认 MediaRouteChooserDialog,则已处理零设备状态。

后续步骤

至此,您可以添加到 Android 发送方应用的功能已全部介绍完毕。现在,您可以为其他平台(iOS 或 Web)构建发送方应用,也可以构建 Web 接收方应用。