ExoPlayer Cast 投屏 Demo 实战指南:用 CastPlayer 与 ExoPlayer 实现投屏与本地播放无缝切换 📅 发布时间:2026/9/20 23:01:47 👁 浏览次数: 音视频移动开发【免费下载链接】ExoPlayerAn extensible media player for Android项目地址https://gitcode.com/gh_mirrors/exop/ExoPlayer点击查看免费下载本指南围绕 ExoPlayer 仓库中的 demos/cast/README.md 展开系统讲解 Cast demo 应用的核心能力如何在同一个界面中基于CastPlayer与ExoPlayer实现 Google Cast 投屏与本地播放的无缝切换以及如何通过自定义OptionsProvider、MediaItemConverter深度定制投屏行为。读完本文你将掌握该 demo 的构建与运行方式、如何注入自己的测试流、如何替换默认接收端应用与 DRM 配置以及底层投屏切换的源码实现原理。一、Demo 概览一个播放器、两种播放通道Cast demo 是 ExoPlayer 众多示例应用见 demos/README.md中专门用于演示Google Cast 投屏能力的一个独立应用。它的核心设计理念是在没有 Cast 设备Chromecast 等时使用普通的ExoPlayer在手机上本地播放一旦检测到可用的 Cast 会话则切换到CastPlayer由投屏接收端receiver app负责解码与渲染两者的播放队列、播放位置、播放状态在切换时会被迁移保留实现“无缝切换”。这种双通道设计的关键在于CastPlayer实现了与ExoPlayer相同的Player接口见 extensions/cast/README.md因此它可以被传入所有接受Player的组件——包括 UI 模块提供的StyledPlayerView等控件业务层无需感知当前究竟是本地播放还是投屏播放。从源码结构看demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/该 demo 由以下几个类协作完成类职责MainActivity承载StyledPlayerView、播放队列列表与“添加样例”对话框注册 Cast 媒体路由按钮PlayerManager同时持有ExoPlayer与CastPlayer负责两者间的切换、媒体队列的增删改、状态迁移DemoUtil集中定义可用的示例媒体流SAMPLES列表DemoApplication启用 multidex 的 Application 类二、构建与运行 Cast demo2.1 从 Android Studio 导入按照 demos/README.md 的说明打开 Android Studio选择File - New - Import Project定位到本仓库根目录ExoPlayer文件夹在运行配置下拉列表中选中 Cast demo点击Run部署到设备。2.2 使用 Gradle 命令行构建在仓库根目录打开终端./gradlew projectsprojects任务会列出所有子项目其中 demo 项目以demo开头。接着查看某个 demo 的可用任务./gradlew :demo名称:tasks从Install tasks一节选择合适的安装任务执行例如./gradlew :demo:installNoDecoderExtensionsDebug该命令以 debug 模式、不带解码器扩展地安装主 demo 应用。Cast demo 的模块名同样以demo前缀区分可在./gradlew projects的输出中确认。2.3 投屏的硬件前提投屏casting需要一台真实的 Android 设备与一个 Cast 设备如 Chromecast并且两者位于同一网络。README 中特别强调测试投屏功能必须把应用部署到真机上模拟器无法完成真实投屏验证。三、测试自己的流向 DemoUtil 注入 MediaItem原文档建议通过向DemoUtil添加带 URI 与 MIME 类型的MediaItem来测试自己的流。DemoUtil位于 demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java它预置了覆盖多种协议的示例流SAMPLES不可变列表HLS 自适应流Apple bipbop 4x3 / 16x9 基本流、Designing For Google Cast 流MIME 类型为MimeTypes.APPLICATION_M3U8DASH 自适应流Tears of steel 的 HD 与 UHD3840x1714版本MIME 类型为MimeTypes.APPLICATION_MPD渐进式视频MP4480x360 Dizzy与 MKV1280x720 ScreensMIME 类型为MimeTypes.VIDEO_MP4带封面的渐进式音频MP3 格式附带MediaMetadata标题、艺术家、专辑、流派、曲目号、封面图 URIWidevine DRM 内容DASH cenc / cbc1 / cbcs 三种加密方案的 Tears 流均通过MediaItem.DrmConfiguration.Builder配置。添加自定义流的典型写法如下new MediaItem.Builder() .setUri(https://your-server/your-stream.m3u8) .setMediaMetadata( new MediaMetadata.Builder() .setTitle(My HLS stream) .build()) .setMimeType(MIME_TYPE_HLS) .build());其中setUri指定媒体 URIsetMimeType告知播放器媒体封装格式HLS 为application/x-mpegURL、DASH 为application/dashxml、SmoothStreaming 为application/vnd.ms-sstrxml、MP4 为video/mp4。这些常量已在DemoUtil中预定义MIME_TYPE_DASH、MIME_TYPE_HLS、MIME_TYPE_SS、MIME_TYPE_VIDEO_MP4。3.1 Widevine DRM 流的注入方式原文档指出Media3 Cast demo 使用的默认接收端应用被定制为支持 DRM 保护的流——DRM 配置通过MediaInfo传递给接收端因此发送端可以直接用MediaItem.DrmConfiguration.Builder填充 Widevine DRM 凭据。DemoUtil中标注Widevine的样例展示了完整写法new MediaItem.Builder() .setUri(Uri.parse(https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd)) .setMimeType(MIME_TYPE_DASH) .setDrmConfiguration( new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID) .setLicenseUri(https://proxy.uat.widevine.com/proxy?providerwidevine_test) .build()) .build());C.WIDEVINE_UUID是 ExoPlayer 中 Widevine 方案 ID 的常量setLicenseUri指定 License 服务器地址。四、用 OptionsProvider 定制 Cast 行为Cast SDK 在应用初始化时会查找一个OptionsProvider来构建CastOptions。demo 应用以及你自己的应用可以通过提供自定义OptionsProvider来定制 Cast SDK 行为。4.1 默认的 OptionsProvider 与 Manifest 注册Cast demo 的 AndroidManifest.xml 中通过meta-data声明了默认实现meta-data android:namecom.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME android:valuecom.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider/DefaultCastOptionsProvider位于 extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastOptionsProvider.java其源码显示它构建了如下CastOptionsnew CastOptions.Builder() .setResumeSavedSession(false) .setEnableReconnectionService(false) .setReceiverApplicationId(APP_ID_DEFAULT_RECEIVER_WITH_DRM) // A12D4273 .setStopReceiverApplicationWhenEndingSession(true) .build();其中APP_ID_DEFAULT_RECEIVER_WITH_DRM A12D4273即带基础 DRM 支持的默认媒体接收端的应用 ID。源码注释也提醒需要更复杂 DRM 鉴权的应用应当自行创建自定义接收端应用。替换为自己的OptionsProvider时把android:value改为自定义类的全限定名即可meta-data android:namecom.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME android:valuecom.example.cast.MyOptionsProvider/4.2 使用不同的 Cast 接收端应用Cast demo 应用本质是一个 Android Cast发送端sender app配合运行在 Cast 设备上的接收端应用工作。如果你已有自己的接收端应用并想快速验证它与CastPlayer的兼容性可以实现一个自定义OptionsProvider把接收端应用 ID 传入CastOptions.Builder.setReceiverApplicationId(...)public class MyOptionsProvider implements OptionsProvider { NonNull Override public CastOptions getCastOptions(Context context) { return new CastOptions.Builder() .setReceiverApplicationId(YOUR_RECEIVER_APP_ID) // 其他选项 .build(); } }如果你希望使用谷歌托管的默认媒体接收端不启用额外 DRM 定制则使用CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID作为应用 ID。注意demo 默认接收端A12D4273仅用于演示。正式产品必须选择并注册你自己的接收端应用 ID不能直接复用 demo 的 ID。4.3 将 Media3 的 MediaItem 转换为 Cast 的 MediaQueueItem投屏时发送端需要把 ExoPlayer 的MediaItem翻译成 Cast API 的MediaQueueItem。demo 使用DefaultMediaItemConverter完成这一转换见 extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultMediaItemConverter.java。如果自定义接收端需要发送自定义数据可以传入自定义的MediaItemConverter实现。从 CastPlayer.java 源码可见CastPlayer提供了多个构造函数// 默认使用 DefaultMediaItemConverter public CastPlayer(CastContext castContext) { this(castContext, new DefaultMediaItemConverter()); } // 传入自定义 MediaItemConverter public CastPlayer(CastContext castContext, MediaItemConverter mediaItemConverter) { ... }转换的调用点在CastPlayer内部维护媒体队列的逻辑中例如mediaQueueItems[i] mediaItemConverter.toMediaQueueItem(mediaItems.get(i))即发送端在构建MediaQueueItem数组时逐个调用转换器。五、媒体会话与通知避免重复通知Cast SDK 为投屏会话提供了媒体会话MediaSession与通知notification支持。如果你的应用已经自行集成了MediaSession投屏会话可能会导致出现重复的通知或会话此时可以关闭 Cast SDK 自带的媒体会话能力public class MyOptionsProvider implements OptionsProvider { NonNull Override public CastOptions getCastOptions(Context context) { return new CastOptions.Builder() .setCastMediaOptions( new CastMediaOptions.Builder() .setMediaSessionEnabled(false) .setNotificationOptions(null) .build()) // 其他选项 .build(); } }其中setMediaSessionEnabled(false)关闭投屏侧的媒体会话setNotificationOptions(null)去除通知配置。六、源码纵深PlayerManager 如何实现无缝切换要理解 demo 的“无缝切换”能力需要读 PlayerManager.java。该类的核心职责可归纳为三点。6.1 双播放器与当前播放器选择构造函数中同时创建两个播放器并根据会话可用性选择初始播放器localPlayer new ExoPlayer.Builder(context).build(); castPlayer new CastPlayer(castContext); castPlayer.setSessionAvailabilityListener(this); setCurrentPlayer(castPlayer.isCastSessionAvailable() ? castPlayer : localPlayer);PlayerManager实现了CastPlayer.SessionAvailabilityListener接口定义见 extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/SessionAvailabilityListener.java当 Cast 会话变为可用或不可用时分别回调Override public void onCastSessionAvailable() { setCurrentPlayer(castPlayer); } Override public void onCastSessionUnavailable() { setCurrentPlayer(localPlayer); }MainActivity则通过CastButtonFactory.setUpMediaRouteButton(...)注册媒体路由按钮MainActivity.java用户点击路由按钮选择 Cast 设备后会话变化即触发上述切换。6.2 切换时的状态迁移setCurrentPlayer(Player)是切换的核心它把旧播放器的播放位置、playWhenReady状态和当前条目索引保存下来再把这些状态连同媒体队列一起迁移到新播放器// 保存旧播放器状态 playbackPositionMs previousPlayer.getCurrentPosition(); playWhenReady previousPlayer.getPlayWhenReady(); currentItemIndex previousPlayer.getCurrentMediaItemIndex(); previousPlayer.stop(); previousPlayer.clearMediaItems(); // 应用到新播放器 currentPlayer.setMediaItems(mediaQueue, currentItemIndex, playbackPositionMs); currentPlayer.setPlayWhenReady(playWhenReady); currentPlayer.prepare();同时StyledPlayerView被重新绑定到当前播放器并针对投屏场景做 UI 调整投屏时控制器常显setControllerShowTimeoutMs(0)并显示投屏连接图标本地播放时恢复默认的超时隐藏逻辑。6.3 媒体队列操作与跨通道一致性PlayerManager提供了addItem、removeItem、moveItem、selectQueueItem等队列操作方法它们会同步更新内部队列与当前播放器的队列。MainActivity中的RecyclerView支持上下拖动排序ItemTouchHelper、左右滑动删除并且队列项会高亮当前播放项——这些交互在本地与投屏两种状态下表现一致正是因为切换时整个媒体队列被整体迁移。另外PlayerManager还通过onTracksChanged检测本地播放器不支持的轨道类型视频/音频并回调Listener.onUnsupportedTrack弹出提示帮助用户区分“设备/解码器不支持”与流本身的问题。七、支持的媒体格式取决于接收端而非发送端原文档明确指出一个容易误解的事实某条流在 Cast 设备上是否受支持主要取决于接收端应用、接收端使用的媒体播放器以及 Cast 设备本身而不是发送端——发送端本质上只提供媒体 URI 与元数据。一般而言Google Cast 及其 Web Receiver 应用支持官方列出的媒体能力与类型容器格式、编解码器、DRM 方案等如果自定义接收端使用了与 Cast Receiver SDK 不同的媒体播放器则可能支持超出参考列表的其他格式或特性Media3 团队不提供接收端应用构建或特定媒体格式在 Cast 设备上兼容性的支持服务这类问题需要查阅官方 Cast 文档中关于构建接收端应用Web Receiver的内容。八、迁移提示与总结最后需要说明仓库中的 Cast demo 与extensions/cast模块当前位于com.google.android.exoplayer2包下源码注释中已标记为Deprecated官方推荐将代码迁移到androidx.media3迁移指南见仓库根目录 media3-migration.sh 与 README.md其中包含同名CastPlayer、DefaultCastOptionsProvider、DefaultMediaItemConverter等类。迁移后本文所述的 API 名称与用法基本一致。综上所述本文覆盖了 Cast demo 从构建、注入测试流、定制OptionsProvider、替换接收端应用到源码级切换原理的完整链路。无论你是想快速验证自己的流与 Cast 的兼容性还是要将投屏能力集成进正式产品都可以以此为起点围绕 demos/cast 与 extensions/cast 继续深入。赞分享音视频移动开发【免费下载链接】ExoPlayerAn extensible media player for Android项目地址https://gitcode.com/gh_mirrors/exop/ExoPlayer点击查看免费下载相关推荐ExoPlayer Cast 扩展实战指南用 CastPlayer 在 Google Cast 与本地播放之间无缝切换ExoPlayer Cast 扩展实战指南用 CastPlayer 在 Google Cast 与本地播放之间无缝切换 本指南以 ExoPlayer 仓库中的音视频移动开发ExoPlayer Cast 扩展模块实战指南用 CastPlayer 控制远端投屏播放ExoPlayer Cast 扩展模块实战指南用 CastPlayer 控制远端投屏播放 导读 本文围绕 ExoPlayer 官方 Cast 扩展模块 ex音视频移动开发SmartTube 仓库中的 ExoPlayer Cast 扩展用统一 Player 接口驱动投屏播放SmartTube 仓库中的 ExoPlayer Cast 扩展用统一 Player 接口驱动投屏播放 导读 本文讲解 SmartTube 仓库内置的 Exo音视频客户端上一篇【2025新范式】从Vim到IDENVCode重构你的开发体验下一篇你的第一个 PR 如何进入 Overleaf开源协作 LaTeX 编辑器贡献完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考