Skip to main content

gsy-cast-and-loopback

使用内核层投屏 SPI(CastCapability + CastProvider)接入 DLNA,或做本地 loopback 回环模拟被投屏端。

跳到安装

来源信息

仓库
CarGuo/GSYVideoPlayer
最近来源活动
2026年9月1日 06:44
检测到的 SKILL.md 语言
中文
星标
21,494
分支
4,316

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
gsy-cast-and-loopback
description
使用内核层投屏 SPI(CastCapability + CastProvider)接入 DLNA,或做本地 loopback 回环模拟被投屏端。
when_to_use
需要接入 DLNA/AirPlay/Chromecast 到已有播放页;做投屏发起端 UI;做集成测试的本地 loopback 接收端;或做多协议聚合发现。
inputs
[{"name":"providers","description":"一个或多个 CastProvider 实例(DLNA 默认由 jUPnP 提供)","required":true}]
outputs
["CastCapability 单例已注册 provider,可发现设备、连接、控制播放/停止。"]
references
[{"file":"gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java"},{"file":"gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastProvider.java"},{"file":"gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastSession.java"},{"file":"gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastListener.java"},{"file":"gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastMediaInfo.java"},{"file":"gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastState.java"},{"file":"gsyVideoPlayer-cast/src/main/java/com/shuyu/gsyvideoplayer/cast/dlna"},{"file":"app/src/main/java/com/example/gsyvideoplayer/CastDemoActivity.java"},{"file":"app/src/main/java/com/example/gsyvideoplayer/cast"},{"file":"doc/CAST_FEATURE_PLAN.md"}]
# gsy-cast-and-loopback ## 顶层架构 ``` CastCapability (单例) ← 主线程 API 入口 ├─ registerProvider(CastProvider) ├─ startDiscovery(Context) / stopDiscovery() ├─ getAvailableDevices(): List<CastDevice> ├─ connect(device, ConnectCallback) : CastSession(异步) ├─ addListener(CastListener) └─ disconnect() ``` 对齐 Media3 `Player` 语义:`CastCapability ≈ Player 池`、`activeSession ≈ 当前 Player`、`CastProvider ≈ 渲染后端`、`CastListener/SessionListener ≈ Player.Listener`。见 [CastCapability](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java) 与 [CastProvider](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastProvider.java) 的头 JavaDoc。 ## 核心 API ### CastCapability | 方法 | 位置 | 说明 | |---|---|---| | `getInstance()` | [#L38-L47](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java#L38-L47) | DCL 单例 | | `registerProvider(CastProvider)` | [#L85-L90](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java#L85-L90) | 注册;`protocol` 相同则覆盖 | | `unregisterProvider(String)` | [#L93-L103](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java#L93-L103) | 反注册并停止其发现 | | `startDiscovery(Context)` | [#L106-L125](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java#L106-L125) | 所有已注册 provider 同时开始发现 | | `stopDiscovery()` | [#L128-L135](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java#L128-L135) | 全部停止 | | `getAvailableDevices()` | [#L138-L146](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java#L138-L146) | 快照,跨 provider 聚合 | | `connect(CastDevice, ConnectCallback)` | [#L154-L183](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java#L154-L183) | 会自动 disconnect 旧会话 | | `disconnect()` | [#L186-L196](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java#L186-L196) | release 当前 session | | `getActiveSession()` | [#L199-L201](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java#L199-L201) | 拿当前会话(可能 null) | | `addListener / removeListener` | [#L203-L209](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastCapability.java#L203-L209) | 主线程 listener | ### CastProvider(SPI 契约) | 方法 | 说明 | |---|---| | `getProtocol(): String` | 全小写协议 id(`dlna` / `chromecast` / `airplay`) | | `startDiscovery(Context, DiscoveryListener)` | 后台线程订阅,回调 postMain | | `stopDiscovery()` | 释放订阅、解绑 Service | | `connect(CastDevice, ConnectCallback): CastSession` | 建立会话;同步返回 session,异步回调结果 | 线程约定:所有 network IO 由 provider 自己后台跑,回调都 postMain。 ### CastSession(媒体控制) 见 [CastSession](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastSession.java): | 方法 | 说明 | |---|---| | `load(CastMediaInfo)` | 推送要投的 URL | | `play() / pause() / stop()` | 播控 | | `seekTo(long ms)` | 定位 | | `setVolume(int 0-100)` / `setMute(boolean)` | 音量 | | `getState(): CastState` | IDLE / CONNECTING / CONNECTED / LOADING / PLAYING / PAUSED / STOPPED / ERROR,见 [CastState](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastState.java) | | `addListener(SessionListener)` | 位置/状态回调 | | `release()` | 归还 | ### CastMediaInfo(要投的媒体) 见 [CastMediaInfo](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/cast/CastMediaInfo.java)。字段:`url`、`title`、`durationMs`、`mimeType`、`headers`、`metadata`(`Map`)。 ## DLNA 参考实现 模块 `gsyvideoplayer-cast` 提供 [JupnpDlnaProvider](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-cast/src/main/java/com/shuyu/gsyvideoplayer/cast/dlna/JupnpDlnaProvider.java) 与 [JupnpDlnaSession](file:///D:/workspace/project/GSYVideoPlayer/gsyVideoPlayer-cast/src/main/java/com/shuyu/gsyvideoplayer/cast/dlna/JupnpDlnaSession.java)。业务代码调用: ```java CastCapability cap = CastCapability.getInstance(); cap.registerProvider(new JupnpDlnaProvider()); cap.addListener(myListener); cap.startDiscovery(context); ``` 发现事件:`CastListener#onDeviceListChanged(List<CastDevice>)`;连接:`cap.connect(devices.get(0), connectCallback)`。 ## 本地 Loopback(Demo 测试用) app 模块下的 [cast/](file:///D:/workspace/project/GSYVideoPlayer/app/src/main/java/com/example/gsyvideoplayer/cast) 提供**可发现的接收端**,方便在同一台设备上完成集成测试: - [DevReceiverService](file:///D:/workspace/project/GSYVideoPlayer/app/src/main/java/com/example/gsyvideoplayer/cast/DevReceiverService.java) —— foreground service,通过 jUPnP 注册"回环设备"。 - [LoopbackDeviceFactory](file:///D:/workspace/project/GSYVideoPlayer/app/src/main/java/com/example/gsyvideoplayer/cast/LoopbackDeviceFactory.java) —— 构造 UPnP MediaRenderer。 - [LoopbackAvTransportService](file:///D:/workspace/project/GSYVideoPlayer/app/src/main/java/com/example/gsyvideoplayer/cast/LoopbackAvTransportService.java) / [LoopbackRenderingControlService](file:///D:/workspace/project/GSYVideoPlayer/app/src/main/java/com/example/gsyvideoplayer/cast/LoopbackRenderingControlService.java) —— AVTransport / RenderingControl 实现。 - [CastReceiverFloatingWindow](file:///D:/workspace/project/GSYVideoPlayer/app/src/main/java/com/example/gsyvideoplayer/cast/CastReceiverFloatingWindow.java) + [CastReceiverPlayer](file:///D:/workspace/project/GSYVideoPlayer/app/src/main/java/com/example/gsyvideoplayer/video/CastReceiverPlayer.java) —— 可视化播放。 ## Demo 对照 - 一体化:[CastDemoActivity](file:///D:/workspace/project/GSYVideoPlayer/app/src/main/java/com/example/gsyvideoplayer/CastDemoActivity.java) - Cast 相关控制页:[SampleCastControlVideo](file:///D:/workspace/project/GSYVideoPlayer/app/src/main/java/com/example/gsyvideoplayer/video/SampleCastControlVideo.java) - 设计与测试:[doc/CAST_FEATURE_PLAN.md](file:///D:/workspace/project/GSYVideoPlayer/doc/CAST_FEATURE_PLAN.md)、[doc/CAST_RECEIVER_DESIGN.md](file:///D:/workspace/project/GSYVideoPlayer/doc/CAST_RECEIVER_DESIGN.md)、[doc/CAST_TEST_PLAYBOOK.md](file:///D:/workspace/project/GSYVideoPlayer/doc/CAST_TEST_PLAYBOOK.md) ## 常见坑 - 不同 provider 上报的 `CastDevice` 需要用 `getProtocol()` 分组;直接把陌生 protocol 传给 `connect` 会拿到 `IllegalStateException`。 - Provider 网络 IO 必须在自己线程,`CastCapability` 不做 main 切换。 - Wi-Fi 多播锁没拿 → DLNA 发现不到设备:Provider 需要 `WifiManager.MulticastLock`。 - Loopback 与真实设备同网段时会同时被发现,Demo 里有过滤示例。
在 GitHub 查看