Repository navigation
Releases: ding2548-ui/DiPlay
Release list
DiPlay 0.2.12(214)· PSA beta 测试版
DiPlay 0.2.12 · PSA 车机版(beta 测试渠道)
这是 PSA 线的 beta 渠道,从
psa-beta分支发布,包名com.shihab.diplay.psabeta
(可与正式包com.shihab.diplay共存安装),发版 tag 走v0.2.12-beta-<run>,
与正式渠道的v0.2.12-<run>互不可见(在线更新各自只看自己的命名空间)。
正式渠道的包请到v0.2.12-的 release 下载。
Android 7.1.2(API 25)/ Qualcomm msm8953 车机上的 CarPlay 接收端。
本页是当前整体状态页,每次发布前更新。
✅ 可用
- 有线 CarPlay(VPN/NCM 通路):设备发现、重枚举、配对、NCM 数据通路、MFi 认证、
0x4300/0x4301、出画面(Video: first frame rendered)、音频(audioType=media)全部正常。
会话稳定性与断线重连已修(第 12、13 条)。 - 无线 CarPlay:
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
恢复 arm64-v8a 后恢复(第 17 条)。 - 外置 Wi-Fi / 同一局域网:✅ 可用。
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
- 界面汉化、零跑档位识别、倒车暂停、iOS 27 视频车内播放(N 挡门控)。
- 方向盘方控:S01 走通道 A(
car.meter.music.BROADCAST的 JSON)—— 实测正确,
切歌时原厂同步切并被立刻暂停、CarPlay 保持播放。 - T03 方控:本版补上通道 B(
com.leapmotor.customkey.music.pauseplay的整数 extras
ICU_MediaSwitch/ICU_MediaKey),并把车机总线接收器改为常开(第 21 条)。待 T03 复测。 - USB 权限弹窗:
MANAGE_USB+ 无障碍按内容判定,实测已通过
(usb auto-confirm: confirmed,S01)。 - 方控音频归属:焦点与 MediaSession 已拿到(
media keys active focusGranted=true session=true)。 - 在线更新(设置 → 在线更新):检查 GitHub 上的新构建 → 自动下载(支持直连 / 代理)→ 静默安装。
- CarPlay 应用列表里那个「回到原车」的图标按钮,名称与图标都是零跑(不再是 BYD)。
🔴 暂不可用 / 待确认
| 症状 | 现状 | 待办 |
|---|---|---|
| T03 方控无反应 | 根因已定位:通道 B 的解析缺失,且注册通道 B 的接收器此前只由默认关闭的调试开关创建 → 真机上从未注册(第 21 条) | 本版已修,待 T03 复测。请把日志发来,按下面「方控复测判据」核对 |
| lwIP 有线通路 | 出画面但没声音、手动断开后要重启应用、整体不如 VPN 稳 | 开关已摘除(第 18 条),代码留着但用不到 |
方控复测判据(S01 / T03 都适用,缺哪条就说明那步没走通):
- 起会话时应看到
car bus listening on 11 actions (wheel channels included)
—— 这是本版新增的,没有它 = 车机总线接收器没起来(第 21 条的根因)。 - 再看到
media keys listening on 11 car actions。 - 按一次方向盘的「下一首」,应出现恰好一行:
media key source=<通道> action=nextOne -> CarPlay 4 sent=true
(T03 的通道名应是com.leapmotor.customkey.music.pauseplay)。 - T03 若仍无反应:看有没有
media key extra-channel unmatched action=... extras=[...]
—— 这行会把该车机真正发的 extras 全量列出来,据此再加映射即可,不用再跑一趟实车。
若报告里一行 media key ... 都没有:说明按键根本没到 DiPlay,
要查的是车机把方控交给谁(原厂 / 蓝牙媒体通路),不是按键转发。
复测时报告里应出现(缺哪条就说明对应那步没走通):
- lwIP:
airplay event connection accepted from ...→airplay video event ready→
Video recovery: requested keyframe sent=true→Video: first frame rendered - VPN:成功则
vpn tun established variant=... address=fe80::2且
airplay listener ready family=IPv6 port=7000 bind=...;失败则attach failed stage=...
加每个vpn establish variant=... rejected ... - 稳定性:整场不再出现
Invalid NTB16 short-packet pad;AirPlay session ended只在
真正拔线时出现 - 方控:
media keys active focusGranted=true session=true,之后每次按键一行
media key source=... action=... -> CarPlay ... sent=true
⚠️ 已知限制
- CI 不再跑 lint 与单元测试,只出 release 包(原先的 check job 太慢)。
这意味着 lint 这道「防止 API 26+ 调用混进 API 25 构建」的自动防线没有了,
改运行时代码时请手动跑一次python D:\Launcher\kotlin_static_check.py <改动的 .kt 文件>。 - APK 必须带
arm64-v8a(第 17 条):为某个 native 库钉 ABI 会把整个进程变成 32 位,
而 32 位会打挂车载热点。CI 里已加断言。 - 有线走 VPN/NCM;lwIP 的设置开关已摘除(第 18 条),代码保留但在 arm64 进程里用不了。
- 开无线 CarPlay 时车机自身没有网络(msm8953 单射频,不支持 STA+GO 并发)。
本版改动(0.2.12)
-
有线 lwIP:地址通告改用真实地址。
原来把常量fe80::2作为有线端点地址,通过 iAP20x4301 carplay-start-session
下发给 iPhone;而 lwIP 接口上的 link-local 地址是启动时按 EUI-64 从网卡 MAC 推导的,
两者不同 → 手机邻居发现(NDP)解析失败 → 永远不发起 AirPlay TCP。
现改为读取 lwIP 自己的真实 link-local 地址。
(VPN 通路不受影响:它的fe80::2是真的被加到 TUN 接口上的。) -
有线 lwIP:回环中继地址族修正。
中继原来硬编码java.net.Socket("127.0.0.1", ...)(IPv4),而 AirPlay 监听绑定在
getLoopbackAddress()=::1(IPv6)。手机即使连进来,中继也会以 Connection refused 断开。
现与同文件的 UDP 中继保持一致,统一使用getLoopbackAddress()。 -
CI 精简并接入 GitHub Releases。
移除 check job(单测 + 五个模块 lint + 三个 assembleDebug),只构建平台签名 release 包;
构建成功后自动发布到 GitHub Releases,tag 为v0.2.12-<run_number>,附 APK。 -
移植「方控学习」(来自零跑线
DiPlay-main2.0)。
设置页新增独立区块,可把车上的按键或车机方控广播逐条绑定到 CarPlay 的五个媒体动作;
只转发学习过的键,顺带免疫车机内部命令回环。底层WheelLearning.kt本就在本线,
缺的是设置入口,这次补上。 -
修好「在线更新」(原实现是死的)。
updateSection()建完控件后立刻把updateMessage/updateActionButton置空,
按钮的进度回写全部落空 —— 点「检查更新」没有任何反应。已去掉这两行赋值。- 安装对话框的「稍后」不清
updateBusy,点一次之后整个区块直到重进页面都是死的。已修。 AppUpdater硬编码的是零跑线的发版规则(tagv2.0-、asset-leapmotor.apk),
在本线永远找不到自己的包。已改为本线的v0.2.12-<run>+mobile-release.apk。- 两条线共用同一个仓库,
/releases/latest有一半概率指向零跑线的包。现在先读
releases.atom(两条线的 release 都在里面),只取v0.2.12-前缀里最大的构建号,
/releases/latest仅作兜底。 currentBuild()的正则要求(数字)闭合,而本线版本名是0.2.12(189-6f275b6c),
解析结果恒为 null → 检查更新会卡在「正在检查更新…」。已放宽为只认左括号。- 更新区块从「关于」页移到设置页顶层(零跑线也在这里),同时保证全局只有一份视图引用。
-
「回到原车」按钮改品牌。
CarPlay 应用列表里的车机图标由 BYD 改为零跑:DEFAULT_OEM_LABEL由"BYD"改为"零跑"
(并迁移一次已存的旧值),默认图标res/raw/ic_car_home.png换成零跑 logo。 -
iOS 27 视频车内播放:修好启用链(原先整条是关着的)。
门控本身(N 挡 →LeapmotorGearMonitor.videoAllowed()→VideoInCarGate)在本线是完整的,
但AirPlayConfig.videoInCar用的是上游 BYD 的开关
BydOutputSettings.videoWhileParkedActive(),而那个开关只存在于 BYD 车辆数据面板里,
在本车机上够不着:BydOutputSettings.available()要装 BYD 包或 fingerprint 含 BYD,
独立 HUD 探测还要 API 28+(本机 25),BydAmapAdapter找的是com.byd.amapservice。
结果/info永远不带videoPlaybackInfo、SETUP 不协商videoPlayback,
iPhone 压根不会把视频交给车机 —— 门控再对也没用。现改为与零跑线一致的无条件true。
注意「提供能力」≠「允许播放」:VideoInCar.allowed初值为 false,
仍由 N 挡轮询放开,收到任何档位数据前一律不放行。
验证点:airplay /info videoInCar=true ... videoPlaybackAllowed=...。 -
补上
CarPlayVideo.detach()。
本线移植时漏了这个函数,而零跑线在CarPlayHostActivity的重连与退出两处都会调它。
缺它的后果是:CarPlay 重连/退出后视频播放器会留在屏上指向一个已经不存在的会话,
且reply()继续往已关闭的 controller 发消息。现补上函数并在两处 teardown 调用。 -
有线 lwIP 灰屏:事件通道的中继地址族不一致(上一版第 2 条的残留半截)。
上一版把中继侧改成了InetAddress.getLoopbackAddress()(本机解析为::1),
但监听侧仍绑着字面量127.0.0.1(AirPlaySession里的LOOPBACK常量)。
于是主控口 7000 通(它两边都用getLoopbackAddress()),
而eventPort/ timing / keepalive 三条全部Connection refused:18:02:55.588 wired lwip proxy accepted port=44738 fd=5 ← 手机连进来了 18:02:55.598 wired lwip relay ended: Connection refused ← 10ms 后中继失败 (整场没有 airplay event connection accepted)事件通道建不起来 →
sendCommand恒返回 false → 每秒一行
Video recovery: requested keyframe sent=false→ 视频 backlog 超 250ms 后等不到关键帧
→shown=0.0fps→ 灰屏。
现三处监听统一走新的listenerBindAddress(),与中继同一个调用;
UDP 中继的目标地址也从硬编码 IPv4 改成getLoopbackAddress()。Android 16 的报告独立印证了这条(报告 876,同一份 APK 的 lwIP 会话):
20:28:05.402 wired lwip proxy accepted port=37591 fd=5 20:28:05.406 wired lwip relay ended: failed to connect to ip6-localhost/[ip] (port 37591) ... connect failed: ECONNREFUSEDip6-localhost就是::1—— 中继拨 IPv6 环回、监听绑 IPv4 字面量,一眼可见。
同一份报告切到 VPN 通路后airplay event connection accepted立刻正常,
也说明问题只出在环回这一处,不在事件通道本身。 -
VPN 通路
Invalid argument:API 25 专有的平台拒绝,本版自证 + 自愈。
同一份 APK 在 Android 16 上 VPN 通路是通的(报告 876:wired VPN service bound之后
48ms 就attach result=started,随后airplay event connection accepted、
Video: first frame rendered)。所以这不是配置写错,是 Android 7 的平台拒收,
而它只回一个裸 EINVAL —— 既不说哪一步,也不说哪个参数。之前查到这里只能靠
adb logcat -s xcertplay-usb抓stage=。
本版把这条路做成自己会说话:establish()按full→no-allow-family→minimal三种配置依次尝试,
每次结果(接受/拒绝 + 异常类型 + message)都写进报告:
vpn tun established variant=.../vpn establish variant=... rejected ...。
哪一档被接受,说明被拒的就是它比上一档多出来的那个开关;一档被接受,有线通路当轮就活了。attach failed stage=...现在进报告(Log.w只进 logcat,报告只收onDiagnostic)。airplay listener ready现在带bind=<地址>,一眼看出监听到底绑在fe80::2还是::。
同时保留一处独立修正:监听地址是 link-local IPv6 时改用
::通配符
(裸 link-local 字面量scope_id为 0,bind()会 EINVAL;零跑线一直绑::所以没这问题)。 -
方控不压制原厂音乐:音频归属在 API 25 上从来没被申请过。
本车机把方向盘键交给「持有音频焦点的那个媒体会话」,所以必须由 DiPlay 拿到焦点 +
激活 MediaSession,原厂播放器才会让位。而本线的归属只由onMediaAudioChanged(true)
触发,那要 iPhone 把音乐流发过来;实测 iPhone 把音频留在车机蓝牙链路上
(整份报告 0 行Audio:,SETUP 只协商了屏幕流type=110),于是永远不触发。
另一条路onIphonePlaying(iPhone 报播放,走 CarPlay 的 now-playing,是通的)
却指向regainFocusLocked()—— 那个函数在 API 25 上是彻底的死代码:
focusRequest只在start()里、且只在 SDK ≥ 26 时才赋值,函数本身也直接
if (SDK_INT < O) return。结果焦点和 MediaSession 都没建立,方控两边都收,两边都切歌。
现让onIphonePlaying走与零跑线一致的updateLocked(playing)(会建会话、拿焦点)。
顺带:CarPlayMediaKeys的诊断行以前只进 logcat(DiPlay-MediaKeys这个 TAG),
报告里完全看不到,所以「焦点到底拿到没有」一直无从判断;现接进报告。- 媒体会话路径的按键改为经
LeapmotorMediaKeys.dispatch转发,与广播路径共用
去重窗口 —— 否则拿到焦点后一次按键会在两条路上各转一次,变成跳两首。
去重窗口的键也从「原始 action」改成「CarPlay 按钮」,
因为同一按在两条路上的名字不同(广播叫nextOne,媒体会话叫next)。 - 按 §58,没有恢复「发 pause 广播压制原厂播放器」那一招(车机会回声导致 CarPlay 自己被暂停)。
-
有线会话 15~63 秒必断:NCM 的「短包填充字节」被当成致命错误。
NcmUsbBridge.drainFrames()原本按wBlockLength % 512 == 0认定这一块后面必须跟一个
0x00 填充字节,否则failSession("Invalid NTB16 short-packet pad")—— 而failSession
会把整个 NCM 桥标记为DeviceUnavailable,于是整个 AirPlay 会话被拆掉重连。
问题在于填充字节属于 USB 传输,不属于 NTB 块:主机只在一个传输的长度正好是端点
maxPacketSize 整数倍时才补一个 0x00,而这里的接收缓冲会把同一个传输里的多块拼在一起,
于是「512 对齐的块」后面紧跟的其实是下一块的开头(NTB16 头以'N'开头,非 0)→ 误判。
192 报告里每一次断开(lwIP 与 VPN 都是)就是这一行,间隔 15s / 3s / 40s / 63s / 32s。
现改为:512 对齐时,后面那个字节是 0 就当作填充跳掉,不是 0 就不动它(留给下一轮解析)。 -
lwIP 断线后重建报 EBUSY,只能手动切模式才恢复。
LwipNative.start()在旧的原生栈没释放时抛USB IPv6 socket error errno=16(EBUSY,
文案在libdiplay_lwip.so里)。而attachLwip()重建会话前没有关闭上一个
LwipSessionNetwork—— 只在 attach 失败时才关,会话正常结束的路径不会关。
192 报告 21:36:55 与 21:37:17 两次 EBUSY,此后 lwIP 一直是死的,直到手动切到 VPN。
现改为在attachLwip()建新会话前先关掉旧的那个。 -
(回归修复)外置 Wi-Fi 被我改坏了,本版修回。
193 把「link-local IPv6 →::通配符」的替换套在了startAirPlayServer的多地址分支上。
bindAll要求一组地址绑在同一个端口,而::会覆盖同组里的其他地址
(无线主机地址里必然带一个 link-local IPv6)→ 每个候选端口都 EADDRINUSE →
BindException("No common AirPlay port available for the selected interface addresses")。
402 报告里每一次无线尝试都是这一句,HotspotReady之后 70ms 就拆。
...
DiPlay 0.2.12(215)· PSA
DiPlay 0.2.12 · PSA 车机版
Android 7.1.2(API 25)/ Qualcomm msm8953 车机上的 CarPlay 接收端。
本页是当前整体状态页,每次发布前更新。
✅ 可用
- 有线 CarPlay(VPN/NCM 通路):设备发现、重枚举、配对、NCM 数据通路、MFi 认证、
0x4300/0x4301、出画面(Video: first frame rendered)、音频(audioType=media)全部正常。
会话稳定性与断线重连已修(第 12、13 条)。 - 无线 CarPlay:
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
恢复 arm64-v8a 后恢复(第 17 条)。 - 外置 Wi-Fi / 同一局域网:✅ 可用。
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
- 界面汉化、零跑档位识别、倒车暂停、iOS 27 视频车内播放(N 挡门控)。
- 方向盘方控:S01 走通道 A(
car.meter.music.BROADCAST的 JSON)—— 实测正确,
切歌时原厂同步切并被立刻暂停、CarPlay 保持播放。 - T03 方控:本版补上通道 B(
com.leapmotor.customkey.music.pauseplay的整数 extras
ICU_MediaSwitch/ICU_MediaKey),并把车机总线接收器改为常开(第 21 条)。待 T03 复测。 - USB 权限弹窗:
MANAGE_USB+ 无障碍按内容判定,实测已通过
(usb auto-confirm: confirmed,S01)。 - 方控音频归属:焦点与 MediaSession 已拿到(
media keys active focusGranted=true session=true)。 - 在线更新(设置 → 在线更新):检查 GitHub 上的新构建 → 自动下载(支持直连 / 代理)→ 静默安装。
- CarPlay 应用列表里那个「回到原车」的图标按钮,名称与图标都是零跑(不再是 BYD)。
🔴 暂不可用 / 待确认
| 症状 | 现状 | 待办 |
|---|---|---|
| T03 方控无反应 | 根因已定位:通道 B 的解析缺失,且注册通道 B 的接收器此前只由默认关闭的调试开关创建 → 真机上从未注册(第 21 条) | 本版已修,待 T03 复测。请把日志发来,按下面「方控复测判据」核对 |
| lwIP 有线通路 | 出画面但没声音、手动断开后要重启应用、整体不如 VPN 稳 | 开关已摘除(第 18 条),代码留着但用不到 |
方控复测判据(S01 / T03 都适用,缺哪条就说明那步没走通):
- 起会话时应看到
car bus listening on 11 actions (wheel channels included)
—— 这是本版新增的,没有它 = 车机总线接收器没起来(第 21 条的根因)。 - 再看到
media keys listening on 11 car actions。 - 按一次方向盘的「下一首」,应出现恰好一行:
media key source=<通道> action=nextOne -> CarPlay 4 sent=true
(T03 的通道名应是com.leapmotor.customkey.music.pauseplay)。 - T03 若仍无反应:看有没有
media key extra-channel unmatched action=... extras=[...]
—— 这行会把该车机真正发的 extras 全量列出来,据此再加映射即可,不用再跑一趟实车。
若报告里一行 media key ... 都没有:说明按键根本没到 DiPlay,
要查的是车机把方控交给谁(原厂 / 蓝牙媒体通路),不是按键转发。
复测时报告里应出现(缺哪条就说明对应那步没走通):
- lwIP:
airplay event connection accepted from ...→airplay video event ready→
Video recovery: requested keyframe sent=true→Video: first frame rendered - VPN:成功则
vpn tun established variant=... address=fe80::2且
airplay listener ready family=IPv6 port=7000 bind=...;失败则attach failed stage=...
加每个vpn establish variant=... rejected ... - 稳定性:整场不再出现
Invalid NTB16 short-packet pad;AirPlay session ended只在
真正拔线时出现 - 方控:
media keys active focusGranted=true session=true,之后每次按键一行
media key source=... action=... -> CarPlay ... sent=true
⚠️ 已知限制
- CI 不再跑 lint 与单元测试,只出 release 包(原先的 check job 太慢)。
这意味着 lint 这道「防止 API 26+ 调用混进 API 25 构建」的自动防线没有了,
改运行时代码时请手动跑一次python D:\Launcher\kotlin_static_check.py <改动的 .kt 文件>。 - APK 必须带
arm64-v8a(第 17 条):为某个 native 库钉 ABI 会把整个进程变成 32 位,
而 32 位会打挂车载热点。CI 里已加断言。 - 有线走 VPN/NCM;lwIP 的设置开关已摘除(第 18 条),代码保留但在 arm64 进程里用不了。
- 开无线 CarPlay 时车机自身没有网络(msm8953 单射频,不支持 STA+GO 并发)。
本版改动(0.2.12)
-
有线 lwIP:地址通告改用真实地址。
原来把常量fe80::2作为有线端点地址,通过 iAP20x4301 carplay-start-session
下发给 iPhone;而 lwIP 接口上的 link-local 地址是启动时按 EUI-64 从网卡 MAC 推导的,
两者不同 → 手机邻居发现(NDP)解析失败 → 永远不发起 AirPlay TCP。
现改为读取 lwIP 自己的真实 link-local 地址。
(VPN 通路不受影响:它的fe80::2是真的被加到 TUN 接口上的。) -
有线 lwIP:回环中继地址族修正。
中继原来硬编码java.net.Socket("127.0.0.1", ...)(IPv4),而 AirPlay 监听绑定在
getLoopbackAddress()=::1(IPv6)。手机即使连进来,中继也会以 Connection refused 断开。
现与同文件的 UDP 中继保持一致,统一使用getLoopbackAddress()。 -
CI 精简并接入 GitHub Releases。
移除 check job(单测 + 五个模块 lint + 三个 assembleDebug),只构建平台签名 release 包;
构建成功后自动发布到 GitHub Releases,tag 为v0.2.12-<run_number>,附 APK。 -
移植「方控学习」(来自零跑线
DiPlay-main2.0)。
设置页新增独立区块,可把车上的按键或车机方控广播逐条绑定到 CarPlay 的五个媒体动作;
只转发学习过的键,顺带免疫车机内部命令回环。底层WheelLearning.kt本就在本线,
缺的是设置入口,这次补上。 -
修好「在线更新」(原实现是死的)。
updateSection()建完控件后立刻把updateMessage/updateActionButton置空,
按钮的进度回写全部落空 —— 点「检查更新」没有任何反应。已去掉这两行赋值。- 安装对话框的「稍后」不清
updateBusy,点一次之后整个区块直到重进页面都是死的。已修。 AppUpdater硬编码的是零跑线的发版规则(tagv2.0-、asset-leapmotor.apk),
在本线永远找不到自己的包。已改为本线的v0.2.12-<run>+mobile-release.apk。- 两条线共用同一个仓库,
/releases/latest有一半概率指向零跑线的包。现在先读
releases.atom(两条线的 release 都在里面),只取v0.2.12-前缀里最大的构建号,
/releases/latest仅作兜底。 currentBuild()的正则要求(数字)闭合,而本线版本名是0.2.12(189-6f275b6c),
解析结果恒为 null → 检查更新会卡在「正在检查更新…」。已放宽为只认左括号。- 更新区块从「关于」页移到设置页顶层(零跑线也在这里),同时保证全局只有一份视图引用。
-
「回到原车」按钮改品牌。
CarPlay 应用列表里的车机图标由 BYD 改为零跑:DEFAULT_OEM_LABEL由"BYD"改为"零跑"
(并迁移一次已存的旧值),默认图标res/raw/ic_car_home.png换成零跑 logo。 -
iOS 27 视频车内播放:修好启用链(原先整条是关着的)。
门控本身(N 挡 →LeapmotorGearMonitor.videoAllowed()→VideoInCarGate)在本线是完整的,
但AirPlayConfig.videoInCar用的是上游 BYD 的开关
BydOutputSettings.videoWhileParkedActive(),而那个开关只存在于 BYD 车辆数据面板里,
在本车机上够不着:BydOutputSettings.available()要装 BYD 包或 fingerprint 含 BYD,
独立 HUD 探测还要 API 28+(本机 25),BydAmapAdapter找的是com.byd.amapservice。
结果/info永远不带videoPlaybackInfo、SETUP 不协商videoPlayback,
iPhone 压根不会把视频交给车机 —— 门控再对也没用。现改为与零跑线一致的无条件true。
注意「提供能力」≠「允许播放」:VideoInCar.allowed初值为 false,
仍由 N 挡轮询放开,收到任何档位数据前一律不放行。
验证点:airplay /info videoInCar=true ... videoPlaybackAllowed=...。 -
补上
CarPlayVideo.detach()。
本线移植时漏了这个函数,而零跑线在CarPlayHostActivity的重连与退出两处都会调它。
缺它的后果是:CarPlay 重连/退出后视频播放器会留在屏上指向一个已经不存在的会话,
且reply()继续往已关闭的 controller 发消息。现补上函数并在两处 teardown 调用。 -
有线 lwIP 灰屏:事件通道的中继地址族不一致(上一版第 2 条的残留半截)。
上一版把中继侧改成了InetAddress.getLoopbackAddress()(本机解析为::1),
但监听侧仍绑着字面量127.0.0.1(AirPlaySession里的LOOPBACK常量)。
于是主控口 7000 通(它两边都用getLoopbackAddress()),
而eventPort/ timing / keepalive 三条全部Connection refused:18:02:55.588 wired lwip proxy accepted port=44738 fd=5 ← 手机连进来了 18:02:55.598 wired lwip relay ended: Connection refused ← 10ms 后中继失败 (整场没有 airplay event connection accepted)事件通道建不起来 →
sendCommand恒返回 false → 每秒一行
Video recovery: requested keyframe sent=false→ 视频 backlog 超 250ms 后等不到关键帧
→shown=0.0fps→ 灰屏。
现三处监听统一走新的listenerBindAddress(),与中继同一个调用;
UDP 中继的目标地址也从硬编码 IPv4 改成getLoopbackAddress()。Android 16 的报告独立印证了这条(报告 876,同一份 APK 的 lwIP 会话):
20:28:05.402 wired lwip proxy accepted port=37591 fd=5 20:28:05.406 wired lwip relay ended: failed to connect to ip6-localhost/[ip] (port 37591) ... connect failed: ECONNREFUSEDip6-localhost就是::1—— 中继拨 IPv6 环回、监听绑 IPv4 字面量,一眼可见。
同一份报告切到 VPN 通路后airplay event connection accepted立刻正常,
也说明问题只出在环回这一处,不在事件通道本身。 -
VPN 通路
Invalid argument:API 25 专有的平台拒绝,本版自证 + 自愈。
同一份 APK 在 Android 16 上 VPN 通路是通的(报告 876:wired VPN service bound之后
48ms 就attach result=started,随后airplay event connection accepted、
Video: first frame rendered)。所以这不是配置写错,是 Android 7 的平台拒收,
而它只回一个裸 EINVAL —— 既不说哪一步,也不说哪个参数。之前查到这里只能靠
adb logcat -s xcertplay-usb抓stage=。
本版把这条路做成自己会说话:establish()按full→no-allow-family→minimal三种配置依次尝试,
每次结果(接受/拒绝 + 异常类型 + message)都写进报告:
vpn tun established variant=.../vpn establish variant=... rejected ...。
哪一档被接受,说明被拒的就是它比上一档多出来的那个开关;一档被接受,有线通路当轮就活了。attach failed stage=...现在进报告(Log.w只进 logcat,报告只收onDiagnostic)。airplay listener ready现在带bind=<地址>,一眼看出监听到底绑在fe80::2还是::。
同时保留一处独立修正:监听地址是 link-local IPv6 时改用
::通配符
(裸 link-local 字面量scope_id为 0,bind()会 EINVAL;零跑线一直绑::所以没这问题)。 -
方控不压制原厂音乐:音频归属在 API 25 上从来没被申请过。
本车机把方向盘键交给「持有音频焦点的那个媒体会话」,所以必须由 DiPlay 拿到焦点 +
激活 MediaSession,原厂播放器才会让位。而本线的归属只由onMediaAudioChanged(true)
触发,那要 iPhone 把音乐流发过来;实测 iPhone 把音频留在车机蓝牙链路上
(整份报告 0 行Audio:,SETUP 只协商了屏幕流type=110),于是永远不触发。
另一条路onIphonePlaying(iPhone 报播放,走 CarPlay 的 now-playing,是通的)
却指向regainFocusLocked()—— 那个函数在 API 25 上是彻底的死代码:
focusRequest只在start()里、且只在 SDK ≥ 26 时才赋值,函数本身也直接
if (SDK_INT < O) return。结果焦点和 MediaSession 都没建立,方控两边都收,两边都切歌。
现让onIphonePlaying走与零跑线一致的updateLocked(playing)(会建会话、拿焦点)。
顺带:CarPlayMediaKeys的诊断行以前只进 logcat(DiPlay-MediaKeys这个 TAG),
报告里完全看不到,所以「焦点到底拿到没有」一直无从判断;现接进报告。- 媒体会话路径的按键改为经
LeapmotorMediaKeys.dispatch转发,与广播路径共用
去重窗口 —— 否则拿到焦点后一次按键会在两条路上各转一次,变成跳两首。
去重窗口的键也从「原始 action」改成「CarPlay 按钮」,
因为同一按在两条路上的名字不同(广播叫nextOne,媒体会话叫next)。 - 按 §58,没有恢复「发 pause 广播压制原厂播放器」那一招(车机会回声导致 CarPlay 自己被暂停)。
-
有线会话 15~63 秒必断:NCM 的「短包填充字节」被当成致命错误。
NcmUsbBridge.drainFrames()原本按wBlockLength % 512 == 0认定这一块后面必须跟一个
0x00 填充字节,否则failSession("Invalid NTB16 short-packet pad")—— 而failSession
会把整个 NCM 桥标记为DeviceUnavailable,于是整个 AirPlay 会话被拆掉重连。
问题在于填充字节属于 USB 传输,不属于 NTB 块:主机只在一个传输的长度正好是端点
maxPacketSize 整数倍时才补一个 0x00,而这里的接收缓冲会把同一个传输里的多块拼在一起,
于是「512 对齐的块」后面紧跟的其实是下一块的开头(NTB16 头以'N'开头,非 0)→ 误判。
192 报告里每一次断开(lwIP 与 VPN 都是)就是这一行,间隔 15s / 3s / 40s / 63s / 32s。
现改为:512 对齐时,后面那个字节是 0 就当作填充跳掉,不是 0 就不动它(留给下一轮解析)。 -
lwIP 断线后重建报 EBUSY,只能手动切模式才恢复。
LwipNative.start()在旧的原生栈没释放时抛USB IPv6 socket error errno=16(EBUSY,
文案在libdiplay_lwip.so里)。而attachLwip()重建会话前没有关闭上一个
LwipSessionNetwork—— 只在 attach 失败时才关,会话正常结束的路径不会关。
192 报告 21:36:55 与 21:37:17 两次 EBUSY,此后 lwIP 一直是死的,直到手动切到 VPN。
现改为在attachLwip()建新会话前先关掉旧的那个。 -
(回归修复)外置 Wi-Fi 被我改坏了,本版修回。
193 把「link-local IPv6 →::通配符」的替换套在了startAirPlayServer的多地址分支上。
bindAll要求一组地址绑在同一个端口,而::会覆盖同组里的其他地址
(无线主机地址里必然带一个 link-local IPv6)→ 每个候选端口都 EADDRINUSE →
BindException("No common AirPlay port available for the selected interface addresses")。
402 报告里每一次无线尝试都是这一句,HotspotReady之后 70ms 就拆。
现只在单地址分支(即 VPN 通路,地址就是fe80::2)做替换,多地址分支恢复原样。 -
有线默认改成 VPN/NCM,lwIP 改为手动开启。
DiPlayPreferences.wiredLwip默认由true改为false。老版本默认开着,
升级后会保留旧选择,所以加了一次性迁移(wired_lwip_defaulted_v2标记):
首次读取时写回false,之后设置里的开关...
DiPlay 0.2.12(213)· PSA
DiPlay 0.2.12 · PSA 车机版
Android 7.1.2(API 25)/ Qualcomm msm8953 车机上的 CarPlay 接收端。
本页是当前整体状态页,每次发布前更新。
✅ 可用
- 有线 CarPlay(VPN/NCM 通路):设备发现、重枚举、配对、NCM 数据通路、MFi 认证、
0x4300/0x4301、出画面(Video: first frame rendered)、音频(audioType=media)全部正常。
会话稳定性与断线重连已修(第 12、13 条)。 - 无线 CarPlay:
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
恢复 arm64-v8a 后恢复(第 17 条)。 - 外置 Wi-Fi / 同一局域网:✅ 可用。
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
- 界面汉化、零跑档位识别、倒车暂停、iOS 27 视频车内播放(N 挡门控)。
- 方向盘方控:S01 走通道 A(
car.meter.music.BROADCAST的 JSON)—— 实测正确,
切歌时原厂同步切并被立刻暂停、CarPlay 保持播放。 - T03 方控:本版补上通道 B(
com.leapmotor.customkey.music.pauseplay的整数 extras
ICU_MediaSwitch/ICU_MediaKey),并把车机总线接收器改为常开(第 21 条)。待 T03 复测。 - USB 权限弹窗:
MANAGE_USB+ 无障碍按内容判定,实测已通过
(usb auto-confirm: confirmed,S01)。 - 方控音频归属:焦点与 MediaSession 已拿到(
media keys active focusGranted=true session=true)。 - 在线更新(设置 → 在线更新):检查 GitHub 上的新构建 → 自动下载(支持直连 / 代理)→ 静默安装。
- CarPlay 应用列表里那个「回到原车」的图标按钮,名称与图标都是零跑(不再是 BYD)。
🔴 暂不可用 / 待确认
| 症状 | 现状 | 待办 |
|---|---|---|
| T03 方控无反应 | 根因已定位:通道 B 的解析缺失,且注册通道 B 的接收器此前只由默认关闭的调试开关创建 → 真机上从未注册(第 21 条) | 本版已修,待 T03 复测。请把日志发来,按下面「方控复测判据」核对 |
| lwIP 有线通路 | 出画面但没声音、手动断开后要重启应用、整体不如 VPN 稳 | 开关已摘除(第 18 条),代码留着但用不到 |
方控复测判据(S01 / T03 都适用,缺哪条就说明那步没走通):
- 起会话时应看到
car bus listening on 11 actions (wheel channels included)
—— 这是本版新增的,没有它 = 车机总线接收器没起来(第 21 条的根因)。 - 再看到
media keys listening on 11 car actions。 - 按一次方向盘的「下一首」,应出现恰好一行:
media key source=<通道> action=nextOne -> CarPlay 4 sent=true
(T03 的通道名应是com.leapmotor.customkey.music.pauseplay)。 - T03 若仍无反应:看有没有
media key extra-channel unmatched action=... extras=[...]
—— 这行会把该车机真正发的 extras 全量列出来,据此再加映射即可,不用再跑一趟实车。
若报告里一行 media key ... 都没有:说明按键根本没到 DiPlay,
要查的是车机把方控交给谁(原厂 / 蓝牙媒体通路),不是按键转发。
复测时报告里应出现(缺哪条就说明对应那步没走通):
- lwIP:
airplay event connection accepted from ...→airplay video event ready→
Video recovery: requested keyframe sent=true→Video: first frame rendered - VPN:成功则
vpn tun established variant=... address=fe80::2且
airplay listener ready family=IPv6 port=7000 bind=...;失败则attach failed stage=...
加每个vpn establish variant=... rejected ... - 稳定性:整场不再出现
Invalid NTB16 short-packet pad;AirPlay session ended只在
真正拔线时出现 - 方控:
media keys active focusGranted=true session=true,之后每次按键一行
media key source=... action=... -> CarPlay ... sent=true
⚠️ 已知限制
- CI 不再跑 lint 与单元测试,只出 release 包(原先的 check job 太慢)。
这意味着 lint 这道「防止 API 26+ 调用混进 API 25 构建」的自动防线没有了,
改运行时代码时请手动跑一次python D:\Launcher\kotlin_static_check.py <改动的 .kt 文件>。 - APK 必须带
arm64-v8a(第 17 条):为某个 native 库钉 ABI 会把整个进程变成 32 位,
而 32 位会打挂车载热点。CI 里已加断言。 - 有线走 VPN/NCM;lwIP 的设置开关已摘除(第 18 条),代码保留但在 arm64 进程里用不了。
- 开无线 CarPlay 时车机自身没有网络(msm8953 单射频,不支持 STA+GO 并发)。
本版改动(0.2.12)
-
有线 lwIP:地址通告改用真实地址。
原来把常量fe80::2作为有线端点地址,通过 iAP20x4301 carplay-start-session
下发给 iPhone;而 lwIP 接口上的 link-local 地址是启动时按 EUI-64 从网卡 MAC 推导的,
两者不同 → 手机邻居发现(NDP)解析失败 → 永远不发起 AirPlay TCP。
现改为读取 lwIP 自己的真实 link-local 地址。
(VPN 通路不受影响:它的fe80::2是真的被加到 TUN 接口上的。) -
有线 lwIP:回环中继地址族修正。
中继原来硬编码java.net.Socket("127.0.0.1", ...)(IPv4),而 AirPlay 监听绑定在
getLoopbackAddress()=::1(IPv6)。手机即使连进来,中继也会以 Connection refused 断开。
现与同文件的 UDP 中继保持一致,统一使用getLoopbackAddress()。 -
CI 精简并接入 GitHub Releases。
移除 check job(单测 + 五个模块 lint + 三个 assembleDebug),只构建平台签名 release 包;
构建成功后自动发布到 GitHub Releases,tag 为v0.2.12-<run_number>,附 APK。 -
移植「方控学习」(来自零跑线
DiPlay-main2.0)。
设置页新增独立区块,可把车上的按键或车机方控广播逐条绑定到 CarPlay 的五个媒体动作;
只转发学习过的键,顺带免疫车机内部命令回环。底层WheelLearning.kt本就在本线,
缺的是设置入口,这次补上。 -
修好「在线更新」(原实现是死的)。
updateSection()建完控件后立刻把updateMessage/updateActionButton置空,
按钮的进度回写全部落空 —— 点「检查更新」没有任何反应。已去掉这两行赋值。- 安装对话框的「稍后」不清
updateBusy,点一次之后整个区块直到重进页面都是死的。已修。 AppUpdater硬编码的是零跑线的发版规则(tagv2.0-、asset-leapmotor.apk),
在本线永远找不到自己的包。已改为本线的v0.2.12-<run>+mobile-release.apk。- 两条线共用同一个仓库,
/releases/latest有一半概率指向零跑线的包。现在先读
releases.atom(两条线的 release 都在里面),只取v0.2.12-前缀里最大的构建号,
/releases/latest仅作兜底。 currentBuild()的正则要求(数字)闭合,而本线版本名是0.2.12(189-6f275b6c),
解析结果恒为 null → 检查更新会卡在「正在检查更新…」。已放宽为只认左括号。- 更新区块从「关于」页移到设置页顶层(零跑线也在这里),同时保证全局只有一份视图引用。
-
「回到原车」按钮改品牌。
CarPlay 应用列表里的车机图标由 BYD 改为零跑:DEFAULT_OEM_LABEL由"BYD"改为"零跑"
(并迁移一次已存的旧值),默认图标res/raw/ic_car_home.png换成零跑 logo。 -
iOS 27 视频车内播放:修好启用链(原先整条是关着的)。
门控本身(N 挡 →LeapmotorGearMonitor.videoAllowed()→VideoInCarGate)在本线是完整的,
但AirPlayConfig.videoInCar用的是上游 BYD 的开关
BydOutputSettings.videoWhileParkedActive(),而那个开关只存在于 BYD 车辆数据面板里,
在本车机上够不着:BydOutputSettings.available()要装 BYD 包或 fingerprint 含 BYD,
独立 HUD 探测还要 API 28+(本机 25),BydAmapAdapter找的是com.byd.amapservice。
结果/info永远不带videoPlaybackInfo、SETUP 不协商videoPlayback,
iPhone 压根不会把视频交给车机 —— 门控再对也没用。现改为与零跑线一致的无条件true。
注意「提供能力」≠「允许播放」:VideoInCar.allowed初值为 false,
仍由 N 挡轮询放开,收到任何档位数据前一律不放行。
验证点:airplay /info videoInCar=true ... videoPlaybackAllowed=...。 -
补上
CarPlayVideo.detach()。
本线移植时漏了这个函数,而零跑线在CarPlayHostActivity的重连与退出两处都会调它。
缺它的后果是:CarPlay 重连/退出后视频播放器会留在屏上指向一个已经不存在的会话,
且reply()继续往已关闭的 controller 发消息。现补上函数并在两处 teardown 调用。 -
有线 lwIP 灰屏:事件通道的中继地址族不一致(上一版第 2 条的残留半截)。
上一版把中继侧改成了InetAddress.getLoopbackAddress()(本机解析为::1),
但监听侧仍绑着字面量127.0.0.1(AirPlaySession里的LOOPBACK常量)。
于是主控口 7000 通(它两边都用getLoopbackAddress()),
而eventPort/ timing / keepalive 三条全部Connection refused:18:02:55.588 wired lwip proxy accepted port=44738 fd=5 ← 手机连进来了 18:02:55.598 wired lwip relay ended: Connection refused ← 10ms 后中继失败 (整场没有 airplay event connection accepted)事件通道建不起来 →
sendCommand恒返回 false → 每秒一行
Video recovery: requested keyframe sent=false→ 视频 backlog 超 250ms 后等不到关键帧
→shown=0.0fps→ 灰屏。
现三处监听统一走新的listenerBindAddress(),与中继同一个调用;
UDP 中继的目标地址也从硬编码 IPv4 改成getLoopbackAddress()。Android 16 的报告独立印证了这条(报告 876,同一份 APK 的 lwIP 会话):
20:28:05.402 wired lwip proxy accepted port=37591 fd=5 20:28:05.406 wired lwip relay ended: failed to connect to ip6-localhost/[ip] (port 37591) ... connect failed: ECONNREFUSEDip6-localhost就是::1—— 中继拨 IPv6 环回、监听绑 IPv4 字面量,一眼可见。
同一份报告切到 VPN 通路后airplay event connection accepted立刻正常,
也说明问题只出在环回这一处,不在事件通道本身。 -
VPN 通路
Invalid argument:API 25 专有的平台拒绝,本版自证 + 自愈。
同一份 APK 在 Android 16 上 VPN 通路是通的(报告 876:wired VPN service bound之后
48ms 就attach result=started,随后airplay event connection accepted、
Video: first frame rendered)。所以这不是配置写错,是 Android 7 的平台拒收,
而它只回一个裸 EINVAL —— 既不说哪一步,也不说哪个参数。之前查到这里只能靠
adb logcat -s xcertplay-usb抓stage=。
本版把这条路做成自己会说话:establish()按full→no-allow-family→minimal三种配置依次尝试,
每次结果(接受/拒绝 + 异常类型 + message)都写进报告:
vpn tun established variant=.../vpn establish variant=... rejected ...。
哪一档被接受,说明被拒的就是它比上一档多出来的那个开关;一档被接受,有线通路当轮就活了。attach failed stage=...现在进报告(Log.w只进 logcat,报告只收onDiagnostic)。airplay listener ready现在带bind=<地址>,一眼看出监听到底绑在fe80::2还是::。
同时保留一处独立修正:监听地址是 link-local IPv6 时改用
::通配符
(裸 link-local 字面量scope_id为 0,bind()会 EINVAL;零跑线一直绑::所以没这问题)。 -
方控不压制原厂音乐:音频归属在 API 25 上从来没被申请过。
本车机把方向盘键交给「持有音频焦点的那个媒体会话」,所以必须由 DiPlay 拿到焦点 +
激活 MediaSession,原厂播放器才会让位。而本线的归属只由onMediaAudioChanged(true)
触发,那要 iPhone 把音乐流发过来;实测 iPhone 把音频留在车机蓝牙链路上
(整份报告 0 行Audio:,SETUP 只协商了屏幕流type=110),于是永远不触发。
另一条路onIphonePlaying(iPhone 报播放,走 CarPlay 的 now-playing,是通的)
却指向regainFocusLocked()—— 那个函数在 API 25 上是彻底的死代码:
focusRequest只在start()里、且只在 SDK ≥ 26 时才赋值,函数本身也直接
if (SDK_INT < O) return。结果焦点和 MediaSession 都没建立,方控两边都收,两边都切歌。
现让onIphonePlaying走与零跑线一致的updateLocked(playing)(会建会话、拿焦点)。
顺带:CarPlayMediaKeys的诊断行以前只进 logcat(DiPlay-MediaKeys这个 TAG),
报告里完全看不到,所以「焦点到底拿到没有」一直无从判断;现接进报告。- 媒体会话路径的按键改为经
LeapmotorMediaKeys.dispatch转发,与广播路径共用
去重窗口 —— 否则拿到焦点后一次按键会在两条路上各转一次,变成跳两首。
去重窗口的键也从「原始 action」改成「CarPlay 按钮」,
因为同一按在两条路上的名字不同(广播叫nextOne,媒体会话叫next)。 - 按 §58,没有恢复「发 pause 广播压制原厂播放器」那一招(车机会回声导致 CarPlay 自己被暂停)。
-
有线会话 15~63 秒必断:NCM 的「短包填充字节」被当成致命错误。
NcmUsbBridge.drainFrames()原本按wBlockLength % 512 == 0认定这一块后面必须跟一个
0x00 填充字节,否则failSession("Invalid NTB16 short-packet pad")—— 而failSession
会把整个 NCM 桥标记为DeviceUnavailable,于是整个 AirPlay 会话被拆掉重连。
问题在于填充字节属于 USB 传输,不属于 NTB 块:主机只在一个传输的长度正好是端点
maxPacketSize 整数倍时才补一个 0x00,而这里的接收缓冲会把同一个传输里的多块拼在一起,
于是「512 对齐的块」后面紧跟的其实是下一块的开头(NTB16 头以'N'开头,非 0)→ 误判。
192 报告里每一次断开(lwIP 与 VPN 都是)就是这一行,间隔 15s / 3s / 40s / 63s / 32s。
现改为:512 对齐时,后面那个字节是 0 就当作填充跳掉,不是 0 就不动它(留给下一轮解析)。 -
lwIP 断线后重建报 EBUSY,只能手动切模式才恢复。
LwipNative.start()在旧的原生栈没释放时抛USB IPv6 socket error errno=16(EBUSY,
文案在libdiplay_lwip.so里)。而attachLwip()重建会话前没有关闭上一个
LwipSessionNetwork—— 只在 attach 失败时才关,会话正常结束的路径不会关。
192 报告 21:36:55 与 21:37:17 两次 EBUSY,此后 lwIP 一直是死的,直到手动切到 VPN。
现改为在attachLwip()建新会话前先关掉旧的那个。 -
(回归修复)外置 Wi-Fi 被我改坏了,本版修回。
193 把「link-local IPv6 →::通配符」的替换套在了startAirPlayServer的多地址分支上。
bindAll要求一组地址绑在同一个端口,而::会覆盖同组里的其他地址
(无线主机地址里必然带一个 link-local IPv6)→ 每个候选端口都 EADDRINUSE →
BindException("No common AirPlay port available for the selected interface addresses")。
402 报告里每一次无线尝试都是这一句,HotspotReady之后 70ms 就拆。
现只在单地址分支(即 VPN 通路,地址就是fe80::2)做替换,多地址分支恢复原样。 -
有线默认改成 VPN/NCM,lwIP 改为手动开启。
DiPlayPreferences.wiredLwip默认由true改为false。老版本默认开着,
升级后会保留旧选择,所以加了一次性迁移(wired_lwip_defaulted_v2标记):
首次读取时写回false,之后设置里的开关...
DiPlay 0.2.12(212)· PSA
DiPlay 0.2.12 · PSA 车机版
Android 7.1.2(API 25)/ Qualcomm msm8953 车机上的 CarPlay 接收端。
本页是当前整体状态页,每次发布前更新。
✅ 可用
- 有线 CarPlay(VPN/NCM 通路):设备发现、重枚举、配对、NCM 数据通路、MFi 认证、
0x4300/0x4301、出画面(Video: first frame rendered)、音频(audioType=media)全部正常。
会话稳定性与断线重连已修(第 12、13 条)。 - 无线 CarPlay:
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
恢复 arm64-v8a 后恢复(第 17 条)。 - 外置 Wi-Fi / 同一局域网:✅ 可用。
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
- 界面汉化、零跑档位识别、倒车暂停、iOS 27 视频车内播放(N 挡门控)。
- 方向盘方控:S01 走通道 A(
car.meter.music.BROADCAST的 JSON)—— 实测正确,
切歌时原厂同步切并被立刻暂停、CarPlay 保持播放。 - T03 方控:本版补上通道 B(
com.leapmotor.customkey.music.pauseplay的整数 extras
ICU_MediaSwitch/ICU_MediaKey),并把车机总线接收器改为常开(第 21 条)。待 T03 复测。 - USB 权限弹窗:
MANAGE_USB+ 无障碍按内容判定,实测已通过
(usb auto-confirm: confirmed,S01)。 - 方控音频归属:焦点与 MediaSession 已拿到(
media keys active focusGranted=true session=true)。 - 在线更新(设置 → 在线更新):检查 GitHub 上的新构建 → 自动下载(支持直连 / 代理)→ 静默安装。
- CarPlay 应用列表里那个「回到原车」的图标按钮,名称与图标都是零跑(不再是 BYD)。
🔴 暂不可用 / 待确认
| 症状 | 现状 | 待办 |
|---|---|---|
| 仪表盘左侧导航区(原厂广播通路) | 设置页新增「仪表盘导航广播测试」,重放原厂高德导航广播,待真机验证 | 点击按钮后看仪表盘左侧有无转向卡片(箭头+距离+路名),再导出报告查 Meter navi test: 行 |
| T03 方控无反应 | 根因已定位:通道 B 的解析缺失,且注册通道 B 的接收器此前只由默认关闭的调试开关创建 → 真机上从未注册(第 21 条) | 本版已修,待 T03 复测。请把日志发来,按下面「方控复测判据」核对 |
| lwIP 有线通路 | 出画面但没声音、手动断开后要重启应用、整体不如 VPN 稳 | 开关已摘除(第 18 条),代码留着但用不到 |
方控复测判据(S01 / T03 都适用,缺哪条就说明那步没走通):
- 起会话时应看到
car bus listening on 11 actions (wheel channels included)
—— 这是本版新增的,没有它 = 车机总线接收器没起来(第 21 条的根因)。 - 再看到
media keys listening on 11 car actions。 - 按一次方向盘的「下一首」,应出现恰好一行:
media key source=<通道> action=nextOne -> CarPlay 4 sent=true
(T03 的通道名应是com.leapmotor.customkey.music.pauseplay)。 - T03 若仍无反应:看有没有
media key extra-channel unmatched action=... extras=[...]
—— 这行会把该车机真正发的 extras 全量列出来,据此再加映射即可,不用再跑一趟实车。
若报告里一行 media key ... 都没有:说明按键根本没到 DiPlay,
要查的是车机把方控交给谁(原厂 / 蓝牙媒体通路),不是按键转发。
复测时报告里应出现(缺哪条就说明对应那步没走通):
- lwIP:
airplay event connection accepted from ...→airplay video event ready→
Video recovery: requested keyframe sent=true→Video: first frame rendered - VPN:成功则
vpn tun established variant=... address=fe80::2且
airplay listener ready family=IPv6 port=7000 bind=...;失败则attach failed stage=...
加每个vpn establish variant=... rejected ... - 稳定性:整场不再出现
Invalid NTB16 short-packet pad;AirPlay session ended只在
真正拔线时出现 - 方控:
media keys active focusGranted=true session=true,之后每次按键一行
media key source=... action=... -> CarPlay ... sent=true
⚠️ 已知限制
- CI 不再跑 lint 与单元测试,只出 release 包(原先的 check job 太慢)。
这意味着 lint 这道「防止 API 26+ 调用混进 API 25 构建」的自动防线没有了,
改运行时代码时请手动跑一次python D:\Launcher\kotlin_static_check.py <改动的 .kt 文件>。 - APK 必须带
arm64-v8a(第 17 条):为某个 native 库钉 ABI 会把整个进程变成 32 位,
而 32 位会打挂车载热点。CI 里已加断言。 - 有线走 VPN/NCM;lwIP 的设置开关已摘除(第 18 条),代码保留但在 arm64 进程里用不了。
- 开无线 CarPlay 时车机自身没有网络(msm8953 单射频,不支持 STA+GO 并发)。
本版改动(0.2.12)
-
有线 lwIP:地址通告改用真实地址。
原来把常量fe80::2作为有线端点地址,通过 iAP20x4301 carplay-start-session
下发给 iPhone;而 lwIP 接口上的 link-local 地址是启动时按 EUI-64 从网卡 MAC 推导的,
两者不同 → 手机邻居发现(NDP)解析失败 → 永远不发起 AirPlay TCP。
现改为读取 lwIP 自己的真实 link-local 地址。
(VPN 通路不受影响:它的fe80::2是真的被加到 TUN 接口上的。) -
有线 lwIP:回环中继地址族修正。
中继原来硬编码java.net.Socket("127.0.0.1", ...)(IPv4),而 AirPlay 监听绑定在
getLoopbackAddress()=::1(IPv6)。手机即使连进来,中继也会以 Connection refused 断开。
现与同文件的 UDP 中继保持一致,统一使用getLoopbackAddress()。 -
CI 精简并接入 GitHub Releases。
移除 check job(单测 + 五个模块 lint + 三个 assembleDebug),只构建平台签名 release 包;
构建成功后自动发布到 GitHub Releases,tag 为v0.2.12-<run_number>,附 APK。 -
移植「方控学习」(来自零跑线
DiPlay-main2.0)。
设置页新增独立区块,可把车上的按键或车机方控广播逐条绑定到 CarPlay 的五个媒体动作;
只转发学习过的键,顺带免疫车机内部命令回环。底层WheelLearning.kt本就在本线,
缺的是设置入口,这次补上。 -
修好「在线更新」(原实现是死的)。
updateSection()建完控件后立刻把updateMessage/updateActionButton置空,
按钮的进度回写全部落空 —— 点「检查更新」没有任何反应。已去掉这两行赋值。- 安装对话框的「稍后」不清
updateBusy,点一次之后整个区块直到重进页面都是死的。已修。 AppUpdater硬编码的是零跑线的发版规则(tagv2.0-、asset-leapmotor.apk),
在本线永远找不到自己的包。已改为本线的v0.2.12-<run>+mobile-release.apk。- 两条线共用同一个仓库,
/releases/latest有一半概率指向零跑线的包。现在先读
releases.atom(两条线的 release 都在里面),只取v0.2.12-前缀里最大的构建号,
/releases/latest仅作兜底。 currentBuild()的正则要求(数字)闭合,而本线版本名是0.2.12(189-6f275b6c),
解析结果恒为 null → 检查更新会卡在「正在检查更新…」。已放宽为只认左括号。- 更新区块从「关于」页移到设置页顶层(零跑线也在这里),同时保证全局只有一份视图引用。
-
「回到原车」按钮改品牌。
CarPlay 应用列表里的车机图标由 BYD 改为零跑:DEFAULT_OEM_LABEL由"BYD"改为"零跑"
(并迁移一次已存的旧值),默认图标res/raw/ic_car_home.png换成零跑 logo。 -
iOS 27 视频车内播放:修好启用链(原先整条是关着的)。
门控本身(N 挡 →LeapmotorGearMonitor.videoAllowed()→VideoInCarGate)在本线是完整的,
但AirPlayConfig.videoInCar用的是上游 BYD 的开关
BydOutputSettings.videoWhileParkedActive(),而那个开关只存在于 BYD 车辆数据面板里,
在本车机上够不着:BydOutputSettings.available()要装 BYD 包或 fingerprint 含 BYD,
独立 HUD 探测还要 API 28+(本机 25),BydAmapAdapter找的是com.byd.amapservice。
结果/info永远不带videoPlaybackInfo、SETUP 不协商videoPlayback,
iPhone 压根不会把视频交给车机 —— 门控再对也没用。现改为与零跑线一致的无条件true。
注意「提供能力」≠「允许播放」:VideoInCar.allowed初值为 false,
仍由 N 挡轮询放开,收到任何档位数据前一律不放行。
验证点:airplay /info videoInCar=true ... videoPlaybackAllowed=...。 -
补上
CarPlayVideo.detach()。
本线移植时漏了这个函数,而零跑线在CarPlayHostActivity的重连与退出两处都会调它。
缺它的后果是:CarPlay 重连/退出后视频播放器会留在屏上指向一个已经不存在的会话,
且reply()继续往已关闭的 controller 发消息。现补上函数并在两处 teardown 调用。 -
有线 lwIP 灰屏:事件通道的中继地址族不一致(上一版第 2 条的残留半截)。
上一版把中继侧改成了InetAddress.getLoopbackAddress()(本机解析为::1),
但监听侧仍绑着字面量127.0.0.1(AirPlaySession里的LOOPBACK常量)。
于是主控口 7000 通(它两边都用getLoopbackAddress()),
而eventPort/ timing / keepalive 三条全部Connection refused:18:02:55.588 wired lwip proxy accepted port=44738 fd=5 ← 手机连进来了 18:02:55.598 wired lwip relay ended: Connection refused ← 10ms 后中继失败 (整场没有 airplay event connection accepted)事件通道建不起来 →
sendCommand恒返回 false → 每秒一行
Video recovery: requested keyframe sent=false→ 视频 backlog 超 250ms 后等不到关键帧
→shown=0.0fps→ 灰屏。
现三处监听统一走新的listenerBindAddress(),与中继同一个调用;
UDP 中继的目标地址也从硬编码 IPv4 改成getLoopbackAddress()。Android 16 的报告独立印证了这条(报告 876,同一份 APK 的 lwIP 会话):
20:28:05.402 wired lwip proxy accepted port=37591 fd=5 20:28:05.406 wired lwip relay ended: failed to connect to ip6-localhost/[ip] (port 37591) ... connect failed: ECONNREFUSEDip6-localhost就是::1—— 中继拨 IPv6 环回、监听绑 IPv4 字面量,一眼可见。
同一份报告切到 VPN 通路后airplay event connection accepted立刻正常,
也说明问题只出在环回这一处,不在事件通道本身。 -
VPN 通路
Invalid argument:API 25 专有的平台拒绝,本版自证 + 自愈。
同一份 APK 在 Android 16 上 VPN 通路是通的(报告 876:wired VPN service bound之后
48ms 就attach result=started,随后airplay event connection accepted、
Video: first frame rendered)。所以这不是配置写错,是 Android 7 的平台拒收,
而它只回一个裸 EINVAL —— 既不说哪一步,也不说哪个参数。之前查到这里只能靠
adb logcat -s xcertplay-usb抓stage=。
本版把这条路做成自己会说话:establish()按full→no-allow-family→minimal三种配置依次尝试,
每次结果(接受/拒绝 + 异常类型 + message)都写进报告:
vpn tun established variant=.../vpn establish variant=... rejected ...。
哪一档被接受,说明被拒的就是它比上一档多出来的那个开关;一档被接受,有线通路当轮就活了。attach failed stage=...现在进报告(Log.w只进 logcat,报告只收onDiagnostic)。airplay listener ready现在带bind=<地址>,一眼看出监听到底绑在fe80::2还是::。
同时保留一处独立修正:监听地址是 link-local IPv6 时改用
::通配符
(裸 link-local 字面量scope_id为 0,bind()会 EINVAL;零跑线一直绑::所以没这问题)。 -
方控不压制原厂音乐:音频归属在 API 25 上从来没被申请过。
本车机把方向盘键交给「持有音频焦点的那个媒体会话」,所以必须由 DiPlay 拿到焦点 +
激活 MediaSession,原厂播放器才会让位。而本线的归属只由onMediaAudioChanged(true)
触发,那要 iPhone 把音乐流发过来;实测 iPhone 把音频留在车机蓝牙链路上
(整份报告 0 行Audio:,SETUP 只协商了屏幕流type=110),于是永远不触发。
另一条路onIphonePlaying(iPhone 报播放,走 CarPlay 的 now-playing,是通的)
却指向regainFocusLocked()—— 那个函数在 API 25 上是彻底的死代码:
focusRequest只在start()里、且只在 SDK ≥ 26 时才赋值,函数本身也直接
if (SDK_INT < O) return。结果焦点和 MediaSession 都没建立,方控两边都收,两边都切歌。
现让onIphonePlaying走与零跑线一致的updateLocked(playing)(会建会话、拿焦点)。
顺带:CarPlayMediaKeys的诊断行以前只进 logcat(DiPlay-MediaKeys这个 TAG),
报告里完全看不到,所以「焦点到底拿到没有」一直无从判断;现接进报告。- 媒体会话路径的按键改为经
LeapmotorMediaKeys.dispatch转发,与广播路径共用
去重窗口 —— 否则拿到焦点后一次按键会在两条路上各转一次,变成跳两首。
去重窗口的键也从「原始 action」改成「CarPlay 按钮」,
因为同一按在两条路上的名字不同(广播叫nextOne,媒体会话叫next)。 - 按 §58,没有恢复「发 pause 广播压制原厂播放器」那一招(车机会回声导致 CarPlay 自己被暂停)。
-
有线会话 15~63 秒必断:NCM 的「短包填充字节」被当成致命错误。
NcmUsbBridge.drainFrames()原本按wBlockLength % 512 == 0认定这一块后面必须跟一个
0x00 填充字节,否则failSession("Invalid NTB16 short-packet pad")—— 而failSession
会把整个 NCM 桥标记为DeviceUnavailable,于是整个 AirPlay 会话被拆掉重连。
问题在于填充字节属于 USB 传输,不属于 NTB 块:主机只在一个传输的长度正好是端点
maxPacketSize 整数倍时才补一个 0x00,而这里的接收缓冲会把同一个传输里的多块拼在一起,
于是「512 对齐的块」后面紧跟的其实是下一块的开头(NTB16 头以'N'开头,非 0)→ 误判。
192 报告里每一次断开(lwIP 与 VPN 都是)就是这一行,间隔 15s / 3s / 40s / 63s / 32s。
现改为:512 对齐时,后面那个字节是 0 就当作填充跳掉,不是 0 就不动它(留给下一轮解析)。 -
lwIP 断线后重建报 EBUSY,只能手动切模式才恢复。
LwipNative.start()在旧的原生栈没释放时抛USB IPv6 socket error errno=16(EBUSY,
文案在libdiplay_lwip.so里)。而attachLwip()重建会话前没有关闭上一个
LwipSessionNetwork—— 只在 attach 失败时才关,会话正常结束的路径不会关。
192 报告 21:36:55 与 21:37:17 两次 EBUSY,此后 lwIP 一直是死的,直到手动切到 VPN。
现改为在attachLwip()建新会话前先关掉旧的那个。 -
(回归修复)外置 Wi-Fi 被我改坏了,本版修回。
193 把「link-local IPv6 →::通配符」的替换套在了startAirPlayServer的多地址分支上。
bindAll要求一组地址绑在同一个端口,而::会覆盖同组里的其他地址
(无线主机地址里必然带一个 link-local IPv6)→ 每个候选端口都 EADDRINUSE →
BindException("No common AirPlay port available for the selected interface addresses")。
402 报告里每一次无线尝试都是这一句,HotspotReady之后 70ms 就拆。
现只在单地址分支(即 VPN 通路,地址就是fe80::2)做替换,多地址分支恢复原样。 -
有线默认改成 VPN/NCM,lwIP 改为手动开启。
`DiPlayPreferences.wi...
DiPlay 0.2.12(210)· PSA
DiPlay 0.2.12 · PSA 车机版
Android 7.1.2(API 25)/ Qualcomm msm8953 车机上的 CarPlay 接收端。
本页是当前整体状态页,每次发布前更新。
✅ 可用
- 有线 CarPlay(VPN/NCM 通路):设备发现、重枚举、配对、NCM 数据通路、MFi 认证、
0x4300/0x4301、出画面(Video: first frame rendered)、音频(audioType=media)全部正常。
会话稳定性与断线重连已修(第 12、13 条)。 - 无线 CarPlay:
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
恢复 arm64-v8a 后恢复(第 17 条)。 - 外置 Wi-Fi / 同一局域网:✅ 可用。
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
- 界面汉化、零跑档位识别、倒车暂停、iOS 27 视频车内播放(N 挡门控)。
- 方向盘方控:S01 走通道 A(
car.meter.music.BROADCAST的 JSON)—— 实测正确,
切歌时原厂同步切并被立刻暂停、CarPlay 保持播放。 - T03 方控:本版补上通道 B(
com.leapmotor.customkey.music.pauseplay的整数 extras
ICU_MediaSwitch/ICU_MediaKey),并把车机总线接收器改为常开(第 21 条)。待 T03 复测。 - USB 权限弹窗:
MANAGE_USB+ 无障碍按内容判定,实测已通过
(usb auto-confirm: confirmed,S01)。 - 方控音频归属:焦点与 MediaSession 已拿到(
media keys active focusGranted=true session=true)。 - 在线更新(设置 → 在线更新):检查 GitHub 上的新构建 → 自动下载(支持直连 / 代理)→ 静默安装。
- CarPlay 应用列表里那个「回到原车」的图标按钮,名称与图标都是零跑(不再是 BYD)。
🔴 暂不可用 / 待确认
| 症状 | 现状 | 待办 |
|---|---|---|
| T03 方控无反应 | 根因已定位:通道 B 的解析缺失,且注册通道 B 的接收器此前只由默认关闭的调试开关创建 → 真机上从未注册(第 21 条) | 本版已修,待 T03 复测。请把日志发来,按下面「方控复测判据」核对 |
| lwIP 有线通路 | 出画面但没声音、手动断开后要重启应用、整体不如 VPN 稳 | 开关已摘除(第 18 条),代码留着但用不到 |
方控复测判据(S01 / T03 都适用,缺哪条就说明那步没走通):
- 起会话时应看到
car bus listening on 11 actions (wheel channels included)
—— 这是本版新增的,没有它 = 车机总线接收器没起来(第 21 条的根因)。 - 再看到
media keys listening on 11 car actions。 - 按一次方向盘的「下一首」,应出现恰好一行:
media key source=<通道> action=nextOne -> CarPlay 4 sent=true
(T03 的通道名应是com.leapmotor.customkey.music.pauseplay)。 - T03 若仍无反应:看有没有
media key extra-channel unmatched action=... extras=[...]
—— 这行会把该车机真正发的 extras 全量列出来,据此再加映射即可,不用再跑一趟实车。
若报告里一行 media key ... 都没有:说明按键根本没到 DiPlay,
要查的是车机把方控交给谁(原厂 / 蓝牙媒体通路),不是按键转发。
复测时报告里应出现(缺哪条就说明对应那步没走通):
- lwIP:
airplay event connection accepted from ...→airplay video event ready→
Video recovery: requested keyframe sent=true→Video: first frame rendered - VPN:成功则
vpn tun established variant=... address=fe80::2且
airplay listener ready family=IPv6 port=7000 bind=...;失败则attach failed stage=...
加每个vpn establish variant=... rejected ... - 稳定性:整场不再出现
Invalid NTB16 short-packet pad;AirPlay session ended只在
真正拔线时出现 - 方控:
media keys active focusGranted=true session=true,之后每次按键一行
media key source=... action=... -> CarPlay ... sent=true
⚠️ 已知限制
- CI 不再跑 lint 与单元测试,只出 release 包(原先的 check job 太慢)。
这意味着 lint 这道「防止 API 26+ 调用混进 API 25 构建」的自动防线没有了,
改运行时代码时请手动跑一次python D:\Launcher\kotlin_static_check.py <改动的 .kt 文件>。 - APK 必须带
arm64-v8a(第 17 条):为某个 native 库钉 ABI 会把整个进程变成 32 位,
而 32 位会打挂车载热点。CI 里已加断言。 - 有线走 VPN/NCM;lwIP 的设置开关已摘除(第 18 条),代码保留但在 arm64 进程里用不了。
- 开无线 CarPlay 时车机自身没有网络(msm8953 单射频,不支持 STA+GO 并发)。
本版改动(0.2.12)
-
有线 lwIP:地址通告改用真实地址。
原来把常量fe80::2作为有线端点地址,通过 iAP20x4301 carplay-start-session
下发给 iPhone;而 lwIP 接口上的 link-local 地址是启动时按 EUI-64 从网卡 MAC 推导的,
两者不同 → 手机邻居发现(NDP)解析失败 → 永远不发起 AirPlay TCP。
现改为读取 lwIP 自己的真实 link-local 地址。
(VPN 通路不受影响:它的fe80::2是真的被加到 TUN 接口上的。) -
有线 lwIP:回环中继地址族修正。
中继原来硬编码java.net.Socket("127.0.0.1", ...)(IPv4),而 AirPlay 监听绑定在
getLoopbackAddress()=::1(IPv6)。手机即使连进来,中继也会以 Connection refused 断开。
现与同文件的 UDP 中继保持一致,统一使用getLoopbackAddress()。 -
CI 精简并接入 GitHub Releases。
移除 check job(单测 + 五个模块 lint + 三个 assembleDebug),只构建平台签名 release 包;
构建成功后自动发布到 GitHub Releases,tag 为v0.2.12-<run_number>,附 APK。 -
移植「方控学习」(来自零跑线
DiPlay-main2.0)。
设置页新增独立区块,可把车上的按键或车机方控广播逐条绑定到 CarPlay 的五个媒体动作;
只转发学习过的键,顺带免疫车机内部命令回环。底层WheelLearning.kt本就在本线,
缺的是设置入口,这次补上。 -
修好「在线更新」(原实现是死的)。
updateSection()建完控件后立刻把updateMessage/updateActionButton置空,
按钮的进度回写全部落空 —— 点「检查更新」没有任何反应。已去掉这两行赋值。- 安装对话框的「稍后」不清
updateBusy,点一次之后整个区块直到重进页面都是死的。已修。 AppUpdater硬编码的是零跑线的发版规则(tagv2.0-、asset-leapmotor.apk),
在本线永远找不到自己的包。已改为本线的v0.2.12-<run>+mobile-release.apk。- 两条线共用同一个仓库,
/releases/latest有一半概率指向零跑线的包。现在先读
releases.atom(两条线的 release 都在里面),只取v0.2.12-前缀里最大的构建号,
/releases/latest仅作兜底。 currentBuild()的正则要求(数字)闭合,而本线版本名是0.2.12(189-6f275b6c),
解析结果恒为 null → 检查更新会卡在「正在检查更新…」。已放宽为只认左括号。- 更新区块从「关于」页移到设置页顶层(零跑线也在这里),同时保证全局只有一份视图引用。
-
「回到原车」按钮改品牌。
CarPlay 应用列表里的车机图标由 BYD 改为零跑:DEFAULT_OEM_LABEL由"BYD"改为"零跑"
(并迁移一次已存的旧值),默认图标res/raw/ic_car_home.png换成零跑 logo。 -
iOS 27 视频车内播放:修好启用链(原先整条是关着的)。
门控本身(N 挡 →LeapmotorGearMonitor.videoAllowed()→VideoInCarGate)在本线是完整的,
但AirPlayConfig.videoInCar用的是上游 BYD 的开关
BydOutputSettings.videoWhileParkedActive(),而那个开关只存在于 BYD 车辆数据面板里,
在本车机上够不着:BydOutputSettings.available()要装 BYD 包或 fingerprint 含 BYD,
独立 HUD 探测还要 API 28+(本机 25),BydAmapAdapter找的是com.byd.amapservice。
结果/info永远不带videoPlaybackInfo、SETUP 不协商videoPlayback,
iPhone 压根不会把视频交给车机 —— 门控再对也没用。现改为与零跑线一致的无条件true。
注意「提供能力」≠「允许播放」:VideoInCar.allowed初值为 false,
仍由 N 挡轮询放开,收到任何档位数据前一律不放行。
验证点:airplay /info videoInCar=true ... videoPlaybackAllowed=...。 -
补上
CarPlayVideo.detach()。
本线移植时漏了这个函数,而零跑线在CarPlayHostActivity的重连与退出两处都会调它。
缺它的后果是:CarPlay 重连/退出后视频播放器会留在屏上指向一个已经不存在的会话,
且reply()继续往已关闭的 controller 发消息。现补上函数并在两处 teardown 调用。 -
有线 lwIP 灰屏:事件通道的中继地址族不一致(上一版第 2 条的残留半截)。
上一版把中继侧改成了InetAddress.getLoopbackAddress()(本机解析为::1),
但监听侧仍绑着字面量127.0.0.1(AirPlaySession里的LOOPBACK常量)。
于是主控口 7000 通(它两边都用getLoopbackAddress()),
而eventPort/ timing / keepalive 三条全部Connection refused:18:02:55.588 wired lwip proxy accepted port=44738 fd=5 ← 手机连进来了 18:02:55.598 wired lwip relay ended: Connection refused ← 10ms 后中继失败 (整场没有 airplay event connection accepted)事件通道建不起来 →
sendCommand恒返回 false → 每秒一行
Video recovery: requested keyframe sent=false→ 视频 backlog 超 250ms 后等不到关键帧
→shown=0.0fps→ 灰屏。
现三处监听统一走新的listenerBindAddress(),与中继同一个调用;
UDP 中继的目标地址也从硬编码 IPv4 改成getLoopbackAddress()。Android 16 的报告独立印证了这条(报告 876,同一份 APK 的 lwIP 会话):
20:28:05.402 wired lwip proxy accepted port=37591 fd=5 20:28:05.406 wired lwip relay ended: failed to connect to ip6-localhost/[ip] (port 37591) ... connect failed: ECONNREFUSEDip6-localhost就是::1—— 中继拨 IPv6 环回、监听绑 IPv4 字面量,一眼可见。
同一份报告切到 VPN 通路后airplay event connection accepted立刻正常,
也说明问题只出在环回这一处,不在事件通道本身。 -
VPN 通路
Invalid argument:API 25 专有的平台拒绝,本版自证 + 自愈。
同一份 APK 在 Android 16 上 VPN 通路是通的(报告 876:wired VPN service bound之后
48ms 就attach result=started,随后airplay event connection accepted、
Video: first frame rendered)。所以这不是配置写错,是 Android 7 的平台拒收,
而它只回一个裸 EINVAL —— 既不说哪一步,也不说哪个参数。之前查到这里只能靠
adb logcat -s xcertplay-usb抓stage=。
本版把这条路做成自己会说话:establish()按full→no-allow-family→minimal三种配置依次尝试,
每次结果(接受/拒绝 + 异常类型 + message)都写进报告:
vpn tun established variant=.../vpn establish variant=... rejected ...。
哪一档被接受,说明被拒的就是它比上一档多出来的那个开关;一档被接受,有线通路当轮就活了。attach failed stage=...现在进报告(Log.w只进 logcat,报告只收onDiagnostic)。airplay listener ready现在带bind=<地址>,一眼看出监听到底绑在fe80::2还是::。
同时保留一处独立修正:监听地址是 link-local IPv6 时改用
::通配符
(裸 link-local 字面量scope_id为 0,bind()会 EINVAL;零跑线一直绑::所以没这问题)。 -
方控不压制原厂音乐:音频归属在 API 25 上从来没被申请过。
本车机把方向盘键交给「持有音频焦点的那个媒体会话」,所以必须由 DiPlay 拿到焦点 +
激活 MediaSession,原厂播放器才会让位。而本线的归属只由onMediaAudioChanged(true)
触发,那要 iPhone 把音乐流发过来;实测 iPhone 把音频留在车机蓝牙链路上
(整份报告 0 行Audio:,SETUP 只协商了屏幕流type=110),于是永远不触发。
另一条路onIphonePlaying(iPhone 报播放,走 CarPlay 的 now-playing,是通的)
却指向regainFocusLocked()—— 那个函数在 API 25 上是彻底的死代码:
focusRequest只在start()里、且只在 SDK ≥ 26 时才赋值,函数本身也直接
if (SDK_INT < O) return。结果焦点和 MediaSession 都没建立,方控两边都收,两边都切歌。
现让onIphonePlaying走与零跑线一致的updateLocked(playing)(会建会话、拿焦点)。
顺带:CarPlayMediaKeys的诊断行以前只进 logcat(DiPlay-MediaKeys这个 TAG),
报告里完全看不到,所以「焦点到底拿到没有」一直无从判断;现接进报告。- 媒体会话路径的按键改为经
LeapmotorMediaKeys.dispatch转发,与广播路径共用
去重窗口 —— 否则拿到焦点后一次按键会在两条路上各转一次,变成跳两首。
去重窗口的键也从「原始 action」改成「CarPlay 按钮」,
因为同一按在两条路上的名字不同(广播叫nextOne,媒体会话叫next)。 - 按 §58,没有恢复「发 pause 广播压制原厂播放器」那一招(车机会回声导致 CarPlay 自己被暂停)。
-
有线会话 15~63 秒必断:NCM 的「短包填充字节」被当成致命错误。
NcmUsbBridge.drainFrames()原本按wBlockLength % 512 == 0认定这一块后面必须跟一个
0x00 填充字节,否则failSession("Invalid NTB16 short-packet pad")—— 而failSession
会把整个 NCM 桥标记为DeviceUnavailable,于是整个 AirPlay 会话被拆掉重连。
问题在于填充字节属于 USB 传输,不属于 NTB 块:主机只在一个传输的长度正好是端点
maxPacketSize 整数倍时才补一个 0x00,而这里的接收缓冲会把同一个传输里的多块拼在一起,
于是「512 对齐的块」后面紧跟的其实是下一块的开头(NTB16 头以'N'开头,非 0)→ 误判。
192 报告里每一次断开(lwIP 与 VPN 都是)就是这一行,间隔 15s / 3s / 40s / 63s / 32s。
现改为:512 对齐时,后面那个字节是 0 就当作填充跳掉,不是 0 就不动它(留给下一轮解析)。 -
lwIP 断线后重建报 EBUSY,只能手动切模式才恢复。
LwipNative.start()在旧的原生栈没释放时抛USB IPv6 socket error errno=16(EBUSY,
文案在libdiplay_lwip.so里)。而attachLwip()重建会话前没有关闭上一个
LwipSessionNetwork—— 只在 attach 失败时才关,会话正常结束的路径不会关。
192 报告 21:36:55 与 21:37:17 两次 EBUSY,此后 lwIP 一直是死的,直到手动切到 VPN。
现改为在attachLwip()建新会话前先关掉旧的那个。 -
(回归修复)外置 Wi-Fi 被我改坏了,本版修回。
193 把「link-local IPv6 →::通配符」的替换套在了startAirPlayServer的多地址分支上。
bindAll要求一组地址绑在同一个端口,而::会覆盖同组里的其他地址
(无线主机地址里必然带一个 link-local IPv6)→ 每个候选端口都 EADDRINUSE →
BindException("No common AirPlay port available for the selected interface addresses")。
402 报告里每一次无线尝试都是这一句,HotspotReady之后 70ms 就拆。
现只在单地址分支(即 VPN 通路,地址就是fe80::2)做替换,多地址分支恢复原样。 -
有线默认改成 VPN/NCM,lwIP 改为手动开启。
DiPlayPreferences.wiredLwip默认由true改为false。老版本默认开着,
升级后会保留旧选择,所以加了一次性迁移(wired_lwip_defaulted_v2标记):
首次读取时写回false,之后设置里的开关...
DiPlay 0.2.12(208)· PSA
DiPlay 0.2.12 · PSA 车机版
Android 7.1.2(API 25)/ Qualcomm msm8953 车机上的 CarPlay 接收端。
本页是当前整体状态页,每次发布前更新。
✅ 可用
- 有线 CarPlay(VPN/NCM 通路):设备发现、重枚举、配对、NCM 数据通路、MFi 认证、
0x4300/0x4301、出画面(Video: first frame rendered)、音频(audioType=media)全部正常。
会话稳定性与断线重连已修(第 12、13 条)。 - 无线 CarPlay:
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
恢复 arm64-v8a 后恢复(第 17 条)。 - 外置 Wi-Fi / 同一局域网:✅ 可用。
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
- 界面汉化、零跑档位识别、倒车暂停、iOS 27 视频车内播放(N 挡门控)。
- 方向盘方控:S01 走通道 A(
car.meter.music.BROADCAST的 JSON)—— 实测正确,
切歌时原厂同步切并被立刻暂停、CarPlay 保持播放。 - T03 方控:本版补上通道 B(
com.leapmotor.customkey.music.pauseplay的整数 extras
ICU_MediaSwitch/ICU_MediaKey),并把车机总线接收器改为常开(第 21 条)。待 T03 复测。 - USB 权限弹窗:
MANAGE_USB+ 无障碍按内容判定,实测已通过
(usb auto-confirm: confirmed,S01)。 - 方控音频归属:焦点与 MediaSession 已拿到(
media keys active focusGranted=true session=true)。 - 在线更新(设置 → 在线更新):检查 GitHub 上的新构建 → 自动下载(支持直连 / 代理)→ 静默安装。
- CarPlay 应用列表里那个「回到原车」的图标按钮,名称与图标都是零跑(不再是 BYD)。
🔴 暂不可用 / 待确认
| 症状 | 现状 | 待办 |
|---|---|---|
| T03 方控无反应 | 根因已定位:通道 B 的解析缺失,且注册通道 B 的接收器此前只由默认关闭的调试开关创建 → 真机上从未注册(第 21 条) | 本版已修,待 T03 复测。请把日志发来,按下面「方控复测判据」核对 |
| lwIP 有线通路 | 出画面但没声音、手动断开后要重启应用、整体不如 VPN 稳 | 开关已摘除(第 18 条),代码留着但用不到 |
方控复测判据(S01 / T03 都适用,缺哪条就说明那步没走通):
- 起会话时应看到
car bus listening on 11 actions (wheel channels included)
—— 这是本版新增的,没有它 = 车机总线接收器没起来(第 21 条的根因)。 - 再看到
media keys listening on 11 car actions。 - 按一次方向盘的「下一首」,应出现恰好一行:
media key source=<通道> action=nextOne -> CarPlay 4 sent=true
(T03 的通道名应是com.leapmotor.customkey.music.pauseplay)。 - T03 若仍无反应:看有没有
media key extra-channel unmatched action=... extras=[...]
—— 这行会把该车机真正发的 extras 全量列出来,据此再加映射即可,不用再跑一趟实车。
若报告里一行 media key ... 都没有:说明按键根本没到 DiPlay,
要查的是车机把方控交给谁(原厂 / 蓝牙媒体通路),不是按键转发。
复测时报告里应出现(缺哪条就说明对应那步没走通):
- lwIP:
airplay event connection accepted from ...→airplay video event ready→
Video recovery: requested keyframe sent=true→Video: first frame rendered - VPN:成功则
vpn tun established variant=... address=fe80::2且
airplay listener ready family=IPv6 port=7000 bind=...;失败则attach failed stage=...
加每个vpn establish variant=... rejected ... - 稳定性:整场不再出现
Invalid NTB16 short-packet pad;AirPlay session ended只在
真正拔线时出现 - 方控:
media keys active focusGranted=true session=true,之后每次按键一行
media key source=... action=... -> CarPlay ... sent=true
⚠️ 已知限制
- CI 不再跑 lint 与单元测试,只出 release 包(原先的 check job 太慢)。
这意味着 lint 这道「防止 API 26+ 调用混进 API 25 构建」的自动防线没有了,
改运行时代码时请手动跑一次python D:\Launcher\kotlin_static_check.py <改动的 .kt 文件>。 - APK 必须带
arm64-v8a(第 17 条):为某个 native 库钉 ABI 会把整个进程变成 32 位,
而 32 位会打挂车载热点。CI 里已加断言。 - 有线走 VPN/NCM;lwIP 的设置开关已摘除(第 18 条),代码保留但在 arm64 进程里用不了。
- 开无线 CarPlay 时车机自身没有网络(msm8953 单射频,不支持 STA+GO 并发)。
本版改动(0.2.12)
-
有线 lwIP:地址通告改用真实地址。
原来把常量fe80::2作为有线端点地址,通过 iAP20x4301 carplay-start-session
下发给 iPhone;而 lwIP 接口上的 link-local 地址是启动时按 EUI-64 从网卡 MAC 推导的,
两者不同 → 手机邻居发现(NDP)解析失败 → 永远不发起 AirPlay TCP。
现改为读取 lwIP 自己的真实 link-local 地址。
(VPN 通路不受影响:它的fe80::2是真的被加到 TUN 接口上的。) -
有线 lwIP:回环中继地址族修正。
中继原来硬编码java.net.Socket("127.0.0.1", ...)(IPv4),而 AirPlay 监听绑定在
getLoopbackAddress()=::1(IPv6)。手机即使连进来,中继也会以 Connection refused 断开。
现与同文件的 UDP 中继保持一致,统一使用getLoopbackAddress()。 -
CI 精简并接入 GitHub Releases。
移除 check job(单测 + 五个模块 lint + 三个 assembleDebug),只构建平台签名 release 包;
构建成功后自动发布到 GitHub Releases,tag 为v0.2.12-<run_number>,附 APK。 -
移植「方控学习」(来自零跑线
DiPlay-main2.0)。
设置页新增独立区块,可把车上的按键或车机方控广播逐条绑定到 CarPlay 的五个媒体动作;
只转发学习过的键,顺带免疫车机内部命令回环。底层WheelLearning.kt本就在本线,
缺的是设置入口,这次补上。 -
修好「在线更新」(原实现是死的)。
updateSection()建完控件后立刻把updateMessage/updateActionButton置空,
按钮的进度回写全部落空 —— 点「检查更新」没有任何反应。已去掉这两行赋值。- 安装对话框的「稍后」不清
updateBusy,点一次之后整个区块直到重进页面都是死的。已修。 AppUpdater硬编码的是零跑线的发版规则(tagv2.0-、asset-leapmotor.apk),
在本线永远找不到自己的包。已改为本线的v0.2.12-<run>+mobile-release.apk。- 两条线共用同一个仓库,
/releases/latest有一半概率指向零跑线的包。现在先读
releases.atom(两条线的 release 都在里面),只取v0.2.12-前缀里最大的构建号,
/releases/latest仅作兜底。 currentBuild()的正则要求(数字)闭合,而本线版本名是0.2.12(189-6f275b6c),
解析结果恒为 null → 检查更新会卡在「正在检查更新…」。已放宽为只认左括号。- 更新区块从「关于」页移到设置页顶层(零跑线也在这里),同时保证全局只有一份视图引用。
-
「回到原车」按钮改品牌。
CarPlay 应用列表里的车机图标由 BYD 改为零跑:DEFAULT_OEM_LABEL由"BYD"改为"零跑"
(并迁移一次已存的旧值),默认图标res/raw/ic_car_home.png换成零跑 logo。 -
iOS 27 视频车内播放:修好启用链(原先整条是关着的)。
门控本身(N 挡 →LeapmotorGearMonitor.videoAllowed()→VideoInCarGate)在本线是完整的,
但AirPlayConfig.videoInCar用的是上游 BYD 的开关
BydOutputSettings.videoWhileParkedActive(),而那个开关只存在于 BYD 车辆数据面板里,
在本车机上够不着:BydOutputSettings.available()要装 BYD 包或 fingerprint 含 BYD,
独立 HUD 探测还要 API 28+(本机 25),BydAmapAdapter找的是com.byd.amapservice。
结果/info永远不带videoPlaybackInfo、SETUP 不协商videoPlayback,
iPhone 压根不会把视频交给车机 —— 门控再对也没用。现改为与零跑线一致的无条件true。
注意「提供能力」≠「允许播放」:VideoInCar.allowed初值为 false,
仍由 N 挡轮询放开,收到任何档位数据前一律不放行。
验证点:airplay /info videoInCar=true ... videoPlaybackAllowed=...。 -
补上
CarPlayVideo.detach()。
本线移植时漏了这个函数,而零跑线在CarPlayHostActivity的重连与退出两处都会调它。
缺它的后果是:CarPlay 重连/退出后视频播放器会留在屏上指向一个已经不存在的会话,
且reply()继续往已关闭的 controller 发消息。现补上函数并在两处 teardown 调用。 -
有线 lwIP 灰屏:事件通道的中继地址族不一致(上一版第 2 条的残留半截)。
上一版把中继侧改成了InetAddress.getLoopbackAddress()(本机解析为::1),
但监听侧仍绑着字面量127.0.0.1(AirPlaySession里的LOOPBACK常量)。
于是主控口 7000 通(它两边都用getLoopbackAddress()),
而eventPort/ timing / keepalive 三条全部Connection refused:18:02:55.588 wired lwip proxy accepted port=44738 fd=5 ← 手机连进来了 18:02:55.598 wired lwip relay ended: Connection refused ← 10ms 后中继失败 (整场没有 airplay event connection accepted)事件通道建不起来 →
sendCommand恒返回 false → 每秒一行
Video recovery: requested keyframe sent=false→ 视频 backlog 超 250ms 后等不到关键帧
→shown=0.0fps→ 灰屏。
现三处监听统一走新的listenerBindAddress(),与中继同一个调用;
UDP 中继的目标地址也从硬编码 IPv4 改成getLoopbackAddress()。Android 16 的报告独立印证了这条(报告 876,同一份 APK 的 lwIP 会话):
20:28:05.402 wired lwip proxy accepted port=37591 fd=5 20:28:05.406 wired lwip relay ended: failed to connect to ip6-localhost/[ip] (port 37591) ... connect failed: ECONNREFUSEDip6-localhost就是::1—— 中继拨 IPv6 环回、监听绑 IPv4 字面量,一眼可见。
同一份报告切到 VPN 通路后airplay event connection accepted立刻正常,
也说明问题只出在环回这一处,不在事件通道本身。 -
VPN 通路
Invalid argument:API 25 专有的平台拒绝,本版自证 + 自愈。
同一份 APK 在 Android 16 上 VPN 通路是通的(报告 876:wired VPN service bound之后
48ms 就attach result=started,随后airplay event connection accepted、
Video: first frame rendered)。所以这不是配置写错,是 Android 7 的平台拒收,
而它只回一个裸 EINVAL —— 既不说哪一步,也不说哪个参数。之前查到这里只能靠
adb logcat -s xcertplay-usb抓stage=。
本版把这条路做成自己会说话:establish()按full→no-allow-family→minimal三种配置依次尝试,
每次结果(接受/拒绝 + 异常类型 + message)都写进报告:
vpn tun established variant=.../vpn establish variant=... rejected ...。
哪一档被接受,说明被拒的就是它比上一档多出来的那个开关;一档被接受,有线通路当轮就活了。attach failed stage=...现在进报告(Log.w只进 logcat,报告只收onDiagnostic)。airplay listener ready现在带bind=<地址>,一眼看出监听到底绑在fe80::2还是::。
同时保留一处独立修正:监听地址是 link-local IPv6 时改用
::通配符
(裸 link-local 字面量scope_id为 0,bind()会 EINVAL;零跑线一直绑::所以没这问题)。 -
方控不压制原厂音乐:音频归属在 API 25 上从来没被申请过。
本车机把方向盘键交给「持有音频焦点的那个媒体会话」,所以必须由 DiPlay 拿到焦点 +
激活 MediaSession,原厂播放器才会让位。而本线的归属只由onMediaAudioChanged(true)
触发,那要 iPhone 把音乐流发过来;实测 iPhone 把音频留在车机蓝牙链路上
(整份报告 0 行Audio:,SETUP 只协商了屏幕流type=110),于是永远不触发。
另一条路onIphonePlaying(iPhone 报播放,走 CarPlay 的 now-playing,是通的)
却指向regainFocusLocked()—— 那个函数在 API 25 上是彻底的死代码:
focusRequest只在start()里、且只在 SDK ≥ 26 时才赋值,函数本身也直接
if (SDK_INT < O) return。结果焦点和 MediaSession 都没建立,方控两边都收,两边都切歌。
现让onIphonePlaying走与零跑线一致的updateLocked(playing)(会建会话、拿焦点)。
顺带:CarPlayMediaKeys的诊断行以前只进 logcat(DiPlay-MediaKeys这个 TAG),
报告里完全看不到,所以「焦点到底拿到没有」一直无从判断;现接进报告。- 媒体会话路径的按键改为经
LeapmotorMediaKeys.dispatch转发,与广播路径共用
去重窗口 —— 否则拿到焦点后一次按键会在两条路上各转一次,变成跳两首。
去重窗口的键也从「原始 action」改成「CarPlay 按钮」,
因为同一按在两条路上的名字不同(广播叫nextOne,媒体会话叫next)。 - 按 §58,没有恢复「发 pause 广播压制原厂播放器」那一招(车机会回声导致 CarPlay 自己被暂停)。
-
有线会话 15~63 秒必断:NCM 的「短包填充字节」被当成致命错误。
NcmUsbBridge.drainFrames()原本按wBlockLength % 512 == 0认定这一块后面必须跟一个
0x00 填充字节,否则failSession("Invalid NTB16 short-packet pad")—— 而failSession
会把整个 NCM 桥标记为DeviceUnavailable,于是整个 AirPlay 会话被拆掉重连。
问题在于填充字节属于 USB 传输,不属于 NTB 块:主机只在一个传输的长度正好是端点
maxPacketSize 整数倍时才补一个 0x00,而这里的接收缓冲会把同一个传输里的多块拼在一起,
于是「512 对齐的块」后面紧跟的其实是下一块的开头(NTB16 头以'N'开头,非 0)→ 误判。
192 报告里每一次断开(lwIP 与 VPN 都是)就是这一行,间隔 15s / 3s / 40s / 63s / 32s。
现改为:512 对齐时,后面那个字节是 0 就当作填充跳掉,不是 0 就不动它(留给下一轮解析)。 -
lwIP 断线后重建报 EBUSY,只能手动切模式才恢复。
LwipNative.start()在旧的原生栈没释放时抛USB IPv6 socket error errno=16(EBUSY,
文案在libdiplay_lwip.so里)。而attachLwip()重建会话前没有关闭上一个
LwipSessionNetwork—— 只在 attach 失败时才关,会话正常结束的路径不会关。
192 报告 21:36:55 与 21:37:17 两次 EBUSY,此后 lwIP 一直是死的,直到手动切到 VPN。
现改为在attachLwip()建新会话前先关掉旧的那个。 -
(回归修复)外置 Wi-Fi 被我改坏了,本版修回。
193 把「link-local IPv6 →::通配符」的替换套在了startAirPlayServer的多地址分支上。
bindAll要求一组地址绑在同一个端口,而::会覆盖同组里的其他地址
(无线主机地址里必然带一个 link-local IPv6)→ 每个候选端口都 EADDRINUSE →
BindException("No common AirPlay port available for the selected interface addresses")。
402 报告里每一次无线尝试都是这一句,HotspotReady之后 70ms 就拆。
现只在单地址分支(即 VPN 通路,地址就是fe80::2)做替换,多地址分支恢复原样。 -
有线默认改成 VPN/NCM,lwIP 改为手动开启。
DiPlayPreferences.wiredLwip默认由true改为false。老版本默认开着,
升级后会保留旧选择,所以加了一次性迁移(wired_lwip_defaulted_v2标记):
首次读取时写回false,之后设置里的开关...
DiPlay 0.2.12(206)· PSA
DiPlay 0.2.12 · PSA 车机版
Android 7.1.2(API 25)/ Qualcomm msm8953 车机上的 CarPlay 接收端。
本页是当前整体状态页,每次发布前更新。
✅ 可用
- 有线 CarPlay(VPN/NCM 通路):设备发现、重枚举、配对、NCM 数据通路、MFi 认证、
0x4300/0x4301、出画面(Video: first frame rendered)、音频(audioType=media)全部正常。
会话稳定性与断线重连已修(第 12、13 条)。 - 无线 CarPlay:
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
恢复 arm64-v8a 后恢复(第 17 条)。 - 外置 Wi-Fi / 同一局域网:✅ 可用。
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
- 界面汉化、零跑档位识别、倒车暂停、iOS 27 视频车内播放(N 挡门控)。
- 方向盘方控:S01 走通道 A(
car.meter.music.BROADCAST的 JSON)—— 实测正确,
切歌时原厂同步切并被立刻暂停、CarPlay 保持播放。 - T03 方控:本版补上通道 B(
com.leapmotor.customkey.music.pauseplay的整数 extras
ICU_MediaSwitch/ICU_MediaKey),并把车机总线接收器改为常开(第 21 条)。待 T03 复测。 - USB 权限弹窗:
MANAGE_USB+ 无障碍按内容判定,实测已通过
(usb auto-confirm: confirmed,S01)。 - 方控音频归属:焦点与 MediaSession 已拿到(
media keys active focusGranted=true session=true)。 - 在线更新(设置 → 在线更新):检查 GitHub 上的新构建 → 自动下载(支持直连 / 代理)→ 静默安装。
- CarPlay 应用列表里那个「回到原车」的图标按钮,名称与图标都是零跑(不再是 BYD)。
🔴 暂不可用 / 待确认
| 症状 | 现状 | 待办 |
|---|---|---|
| T03 方控无反应 | 根因已定位:通道 B 的解析缺失,且注册通道 B 的接收器此前只由默认关闭的调试开关创建 → 真机上从未注册(第 21 条) | 本版已修,待 T03 复测。请把日志发来,按下面「方控复测判据」核对 |
| lwIP 有线通路 | 出画面但没声音、手动断开后要重启应用、整体不如 VPN 稳 | 开关已摘除(第 18 条),代码留着但用不到 |
方控复测判据(S01 / T03 都适用,缺哪条就说明那步没走通):
- 起会话时应看到
car bus listening on 11 actions (wheel channels included)
—— 这是本版新增的,没有它 = 车机总线接收器没起来(第 21 条的根因)。 - 再看到
media keys listening on 11 car actions。 - 按一次方向盘的「下一首」,应出现恰好一行:
media key source=<通道> action=nextOne -> CarPlay 4 sent=true
(T03 的通道名应是com.leapmotor.customkey.music.pauseplay)。 - T03 若仍无反应:看有没有
media key extra-channel unmatched action=... extras=[...]
—— 这行会把该车机真正发的 extras 全量列出来,据此再加映射即可,不用再跑一趟实车。
若报告里一行 media key ... 都没有:说明按键根本没到 DiPlay,
要查的是车机把方控交给谁(原厂 / 蓝牙媒体通路),不是按键转发。
复测时报告里应出现(缺哪条就说明对应那步没走通):
- lwIP:
airplay event connection accepted from ...→airplay video event ready→
Video recovery: requested keyframe sent=true→Video: first frame rendered - VPN:成功则
vpn tun established variant=... address=fe80::2且
airplay listener ready family=IPv6 port=7000 bind=...;失败则attach failed stage=...
加每个vpn establish variant=... rejected ... - 稳定性:整场不再出现
Invalid NTB16 short-packet pad;AirPlay session ended只在
真正拔线时出现 - 方控:
media keys active focusGranted=true session=true,之后每次按键一行
media key source=... action=... -> CarPlay ... sent=true
⚠️ 已知限制
- CI 不再跑 lint 与单元测试,只出 release 包(原先的 check job 太慢)。
这意味着 lint 这道「防止 API 26+ 调用混进 API 25 构建」的自动防线没有了,
改运行时代码时请手动跑一次python D:\Launcher\kotlin_static_check.py <改动的 .kt 文件>。 - APK 必须带
arm64-v8a(第 17 条):为某个 native 库钉 ABI 会把整个进程变成 32 位,
而 32 位会打挂车载热点。CI 里已加断言。 - 有线走 VPN/NCM;lwIP 的设置开关已摘除(第 18 条),代码保留但在 arm64 进程里用不了。
- 开无线 CarPlay 时车机自身没有网络(msm8953 单射频,不支持 STA+GO 并发)。
本版改动(0.2.12)
-
有线 lwIP:地址通告改用真实地址。
原来把常量fe80::2作为有线端点地址,通过 iAP20x4301 carplay-start-session
下发给 iPhone;而 lwIP 接口上的 link-local 地址是启动时按 EUI-64 从网卡 MAC 推导的,
两者不同 → 手机邻居发现(NDP)解析失败 → 永远不发起 AirPlay TCP。
现改为读取 lwIP 自己的真实 link-local 地址。
(VPN 通路不受影响:它的fe80::2是真的被加到 TUN 接口上的。) -
有线 lwIP:回环中继地址族修正。
中继原来硬编码java.net.Socket("127.0.0.1", ...)(IPv4),而 AirPlay 监听绑定在
getLoopbackAddress()=::1(IPv6)。手机即使连进来,中继也会以 Connection refused 断开。
现与同文件的 UDP 中继保持一致,统一使用getLoopbackAddress()。 -
CI 精简并接入 GitHub Releases。
移除 check job(单测 + 五个模块 lint + 三个 assembleDebug),只构建平台签名 release 包;
构建成功后自动发布到 GitHub Releases,tag 为v0.2.12-<run_number>,附 APK。 -
移植「方控学习」(来自零跑线
DiPlay-main2.0)。
设置页新增独立区块,可把车上的按键或车机方控广播逐条绑定到 CarPlay 的五个媒体动作;
只转发学习过的键,顺带免疫车机内部命令回环。底层WheelLearning.kt本就在本线,
缺的是设置入口,这次补上。 -
修好「在线更新」(原实现是死的)。
updateSection()建完控件后立刻把updateMessage/updateActionButton置空,
按钮的进度回写全部落空 —— 点「检查更新」没有任何反应。已去掉这两行赋值。- 安装对话框的「稍后」不清
updateBusy,点一次之后整个区块直到重进页面都是死的。已修。 AppUpdater硬编码的是零跑线的发版规则(tagv2.0-、asset-leapmotor.apk),
在本线永远找不到自己的包。已改为本线的v0.2.12-<run>+mobile-release.apk。- 两条线共用同一个仓库,
/releases/latest有一半概率指向零跑线的包。现在先读
releases.atom(两条线的 release 都在里面),只取v0.2.12-前缀里最大的构建号,
/releases/latest仅作兜底。 currentBuild()的正则要求(数字)闭合,而本线版本名是0.2.12(189-6f275b6c),
解析结果恒为 null → 检查更新会卡在「正在检查更新…」。已放宽为只认左括号。- 更新区块从「关于」页移到设置页顶层(零跑线也在这里),同时保证全局只有一份视图引用。
-
「回到原车」按钮改品牌。
CarPlay 应用列表里的车机图标由 BYD 改为零跑:DEFAULT_OEM_LABEL由"BYD"改为"零跑"
(并迁移一次已存的旧值),默认图标res/raw/ic_car_home.png换成零跑 logo。 -
iOS 27 视频车内播放:修好启用链(原先整条是关着的)。
门控本身(N 挡 →LeapmotorGearMonitor.videoAllowed()→VideoInCarGate)在本线是完整的,
但AirPlayConfig.videoInCar用的是上游 BYD 的开关
BydOutputSettings.videoWhileParkedActive(),而那个开关只存在于 BYD 车辆数据面板里,
在本车机上够不着:BydOutputSettings.available()要装 BYD 包或 fingerprint 含 BYD,
独立 HUD 探测还要 API 28+(本机 25),BydAmapAdapter找的是com.byd.amapservice。
结果/info永远不带videoPlaybackInfo、SETUP 不协商videoPlayback,
iPhone 压根不会把视频交给车机 —— 门控再对也没用。现改为与零跑线一致的无条件true。
注意「提供能力」≠「允许播放」:VideoInCar.allowed初值为 false,
仍由 N 挡轮询放开,收到任何档位数据前一律不放行。
验证点:airplay /info videoInCar=true ... videoPlaybackAllowed=...。 -
补上
CarPlayVideo.detach()。
本线移植时漏了这个函数,而零跑线在CarPlayHostActivity的重连与退出两处都会调它。
缺它的后果是:CarPlay 重连/退出后视频播放器会留在屏上指向一个已经不存在的会话,
且reply()继续往已关闭的 controller 发消息。现补上函数并在两处 teardown 调用。 -
有线 lwIP 灰屏:事件通道的中继地址族不一致(上一版第 2 条的残留半截)。
上一版把中继侧改成了InetAddress.getLoopbackAddress()(本机解析为::1),
但监听侧仍绑着字面量127.0.0.1(AirPlaySession里的LOOPBACK常量)。
于是主控口 7000 通(它两边都用getLoopbackAddress()),
而eventPort/ timing / keepalive 三条全部Connection refused:18:02:55.588 wired lwip proxy accepted port=44738 fd=5 ← 手机连进来了 18:02:55.598 wired lwip relay ended: Connection refused ← 10ms 后中继失败 (整场没有 airplay event connection accepted)事件通道建不起来 →
sendCommand恒返回 false → 每秒一行
Video recovery: requested keyframe sent=false→ 视频 backlog 超 250ms 后等不到关键帧
→shown=0.0fps→ 灰屏。
现三处监听统一走新的listenerBindAddress(),与中继同一个调用;
UDP 中继的目标地址也从硬编码 IPv4 改成getLoopbackAddress()。Android 16 的报告独立印证了这条(报告 876,同一份 APK 的 lwIP 会话):
20:28:05.402 wired lwip proxy accepted port=37591 fd=5 20:28:05.406 wired lwip relay ended: failed to connect to ip6-localhost/[ip] (port 37591) ... connect failed: ECONNREFUSEDip6-localhost就是::1—— 中继拨 IPv6 环回、监听绑 IPv4 字面量,一眼可见。
同一份报告切到 VPN 通路后airplay event connection accepted立刻正常,
也说明问题只出在环回这一处,不在事件通道本身。 -
VPN 通路
Invalid argument:API 25 专有的平台拒绝,本版自证 + 自愈。
同一份 APK 在 Android 16 上 VPN 通路是通的(报告 876:wired VPN service bound之后
48ms 就attach result=started,随后airplay event connection accepted、
Video: first frame rendered)。所以这不是配置写错,是 Android 7 的平台拒收,
而它只回一个裸 EINVAL —— 既不说哪一步,也不说哪个参数。之前查到这里只能靠
adb logcat -s xcertplay-usb抓stage=。
本版把这条路做成自己会说话:establish()按full→no-allow-family→minimal三种配置依次尝试,
每次结果(接受/拒绝 + 异常类型 + message)都写进报告:
vpn tun established variant=.../vpn establish variant=... rejected ...。
哪一档被接受,说明被拒的就是它比上一档多出来的那个开关;一档被接受,有线通路当轮就活了。attach failed stage=...现在进报告(Log.w只进 logcat,报告只收onDiagnostic)。airplay listener ready现在带bind=<地址>,一眼看出监听到底绑在fe80::2还是::。
同时保留一处独立修正:监听地址是 link-local IPv6 时改用
::通配符
(裸 link-local 字面量scope_id为 0,bind()会 EINVAL;零跑线一直绑::所以没这问题)。 -
方控不压制原厂音乐:音频归属在 API 25 上从来没被申请过。
本车机把方向盘键交给「持有音频焦点的那个媒体会话」,所以必须由 DiPlay 拿到焦点 +
激活 MediaSession,原厂播放器才会让位。而本线的归属只由onMediaAudioChanged(true)
触发,那要 iPhone 把音乐流发过来;实测 iPhone 把音频留在车机蓝牙链路上
(整份报告 0 行Audio:,SETUP 只协商了屏幕流type=110),于是永远不触发。
另一条路onIphonePlaying(iPhone 报播放,走 CarPlay 的 now-playing,是通的)
却指向regainFocusLocked()—— 那个函数在 API 25 上是彻底的死代码:
focusRequest只在start()里、且只在 SDK ≥ 26 时才赋值,函数本身也直接
if (SDK_INT < O) return。结果焦点和 MediaSession 都没建立,方控两边都收,两边都切歌。
现让onIphonePlaying走与零跑线一致的updateLocked(playing)(会建会话、拿焦点)。
顺带:CarPlayMediaKeys的诊断行以前只进 logcat(DiPlay-MediaKeys这个 TAG),
报告里完全看不到,所以「焦点到底拿到没有」一直无从判断;现接进报告。- 媒体会话路径的按键改为经
LeapmotorMediaKeys.dispatch转发,与广播路径共用
去重窗口 —— 否则拿到焦点后一次按键会在两条路上各转一次,变成跳两首。
去重窗口的键也从「原始 action」改成「CarPlay 按钮」,
因为同一按在两条路上的名字不同(广播叫nextOne,媒体会话叫next)。 - 按 §58,没有恢复「发 pause 广播压制原厂播放器」那一招(车机会回声导致 CarPlay 自己被暂停)。
-
有线会话 15~63 秒必断:NCM 的「短包填充字节」被当成致命错误。
NcmUsbBridge.drainFrames()原本按wBlockLength % 512 == 0认定这一块后面必须跟一个
0x00 填充字节,否则failSession("Invalid NTB16 short-packet pad")—— 而failSession
会把整个 NCM 桥标记为DeviceUnavailable,于是整个 AirPlay 会话被拆掉重连。
问题在于填充字节属于 USB 传输,不属于 NTB 块:主机只在一个传输的长度正好是端点
maxPacketSize 整数倍时才补一个 0x00,而这里的接收缓冲会把同一个传输里的多块拼在一起,
于是「512 对齐的块」后面紧跟的其实是下一块的开头(NTB16 头以'N'开头,非 0)→ 误判。
192 报告里每一次断开(lwIP 与 VPN 都是)就是这一行,间隔 15s / 3s / 40s / 63s / 32s。
现改为:512 对齐时,后面那个字节是 0 就当作填充跳掉,不是 0 就不动它(留给下一轮解析)。 -
lwIP 断线后重建报 EBUSY,只能手动切模式才恢复。
LwipNative.start()在旧的原生栈没释放时抛USB IPv6 socket error errno=16(EBUSY,
文案在libdiplay_lwip.so里)。而attachLwip()重建会话前没有关闭上一个
LwipSessionNetwork—— 只在 attach 失败时才关,会话正常结束的路径不会关。
192 报告 21:36:55 与 21:37:17 两次 EBUSY,此后 lwIP 一直是死的,直到手动切到 VPN。
现改为在attachLwip()建新会话前先关掉旧的那个。 -
(回归修复)外置 Wi-Fi 被我改坏了,本版修回。
193 把「link-local IPv6 →::通配符」的替换套在了startAirPlayServer的多地址分支上。
bindAll要求一组地址绑在同一个端口,而::会覆盖同组里的其他地址
(无线主机地址里必然带一个 link-local IPv6)→ 每个候选端口都 EADDRINUSE →
BindException("No common AirPlay port available for the selected interface addresses")。
402 报告里每一次无线尝试都是这一句,HotspotReady之后 70ms 就拆。
现只在单地址分支(即 VPN 通路,地址就是fe80::2)做替换,多地址分支恢复原样。 -
有线默认改成 VPN/NCM,lwIP 改为手动开启。
DiPlayPreferences.wiredLwip默认由true改为false。老版本默认开着,
升级后会保留旧选择,所以加了一次性迁移(wired_lwip_defaulted_v2标记):
首次读取时写回false,之后设置里的开关...
DiPlay 0.2.12(203)· PSA
DiPlay 0.2.12 · PSA 车机版
Android 7.1.2(API 25)/ Qualcomm msm8953 车机上的 CarPlay 接收端。
本页是当前整体状态页,每次发布前更新。
✅ 可用
- 有线 CarPlay(VPN/NCM 通路):设备发现、重枚举、配对、NCM 数据通路、MFi 认证、
0x4300/0x4301、出画面(Video: first frame rendered)、音频(audioType=media)全部正常。
会话稳定性与断线重连已修(第 12、13 条)。 - 无线 CarPlay:
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
恢复 arm64-v8a 后恢复(第 17 条)。 - 外置 Wi-Fi / 同一局域网:✅ 可用。
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
- 界面汉化、零跑档位识别、倒车暂停、iOS 27 视频车内播放(N 挡门控)。
- 方向盘方控:S01 走通道 A(
car.meter.music.BROADCAST的 JSON)—— 实测正确,
切歌时原厂同步切并被立刻暂停、CarPlay 保持播放。 - T03 方控:本版补上通道 B(
com.leapmotor.customkey.music.pauseplay的整数 extras
ICU_MediaSwitch/ICU_MediaKey),并把车机总线接收器改为常开(第 21 条)。待 T03 复测。 - USB 权限弹窗:
MANAGE_USB+ 无障碍按内容判定,实测已通过
(usb auto-confirm: confirmed,S01)。 - 方控音频归属:焦点与 MediaSession 已拿到(
media keys active focusGranted=true session=true)。 - 在线更新(设置 → 在线更新):检查 GitHub 上的新构建 → 自动下载(支持直连 / 代理)→ 静默安装。
- CarPlay 应用列表里那个「回到原车」的图标按钮,名称与图标都是零跑(不再是 BYD)。
🔴 暂不可用 / 待确认
| 症状 | 现状 | 待办 |
|---|---|---|
| T03 方控无反应 | 根因已定位:通道 B 的解析缺失,且注册通道 B 的接收器此前只由默认关闭的调试开关创建 → 真机上从未注册(第 21 条) | 本版已修,待 T03 复测。请把日志发来,按下面「方控复测判据」核对 |
| lwIP 有线通路 | 出画面但没声音、手动断开后要重启应用、整体不如 VPN 稳 | 开关已摘除(第 18 条),代码留着但用不到 |
方控复测判据(S01 / T03 都适用,缺哪条就说明那步没走通):
- 起会话时应看到
car bus listening on 11 actions (wheel channels included)
—— 这是本版新增的,没有它 = 车机总线接收器没起来(第 21 条的根因)。 - 再看到
media keys listening on 11 car actions。 - 按一次方向盘的「下一首」,应出现恰好一行:
media key source=<通道> action=nextOne -> CarPlay 4 sent=true
(T03 的通道名应是com.leapmotor.customkey.music.pauseplay)。 - T03 若仍无反应:看有没有
media key extra-channel unmatched action=... extras=[...]
—— 这行会把该车机真正发的 extras 全量列出来,据此再加映射即可,不用再跑一趟实车。
若报告里一行 media key ... 都没有:说明按键根本没到 DiPlay,
要查的是车机把方控交给谁(原厂 / 蓝牙媒体通路),不是按键转发。
复测时报告里应出现(缺哪条就说明对应那步没走通):
- lwIP:
airplay event connection accepted from ...→airplay video event ready→
Video recovery: requested keyframe sent=true→Video: first frame rendered - VPN:成功则
vpn tun established variant=... address=fe80::2且
airplay listener ready family=IPv6 port=7000 bind=...;失败则attach failed stage=...
加每个vpn establish variant=... rejected ... - 稳定性:整场不再出现
Invalid NTB16 short-packet pad;AirPlay session ended只在
真正拔线时出现 - 方控:
media keys active focusGranted=true session=true,之后每次按键一行
media key source=... action=... -> CarPlay ... sent=true
⚠️ 已知限制
- CI 不再跑 lint 与单元测试,只出 release 包(原先的 check job 太慢)。
这意味着 lint 这道「防止 API 26+ 调用混进 API 25 构建」的自动防线没有了,
改运行时代码时请手动跑一次python D:\Launcher\kotlin_static_check.py <改动的 .kt 文件>。 - APK 必须带
arm64-v8a(第 17 条):为某个 native 库钉 ABI 会把整个进程变成 32 位,
而 32 位会打挂车载热点。CI 里已加断言。 - 有线走 VPN/NCM;lwIP 的设置开关已摘除(第 18 条),代码保留但在 arm64 进程里用不了。
- 开无线 CarPlay 时车机自身没有网络(msm8953 单射频,不支持 STA+GO 并发)。
本版改动(0.2.12)
-
有线 lwIP:地址通告改用真实地址。
原来把常量fe80::2作为有线端点地址,通过 iAP20x4301 carplay-start-session
下发给 iPhone;而 lwIP 接口上的 link-local 地址是启动时按 EUI-64 从网卡 MAC 推导的,
两者不同 → 手机邻居发现(NDP)解析失败 → 永远不发起 AirPlay TCP。
现改为读取 lwIP 自己的真实 link-local 地址。
(VPN 通路不受影响:它的fe80::2是真的被加到 TUN 接口上的。) -
有线 lwIP:回环中继地址族修正。
中继原来硬编码java.net.Socket("127.0.0.1", ...)(IPv4),而 AirPlay 监听绑定在
getLoopbackAddress()=::1(IPv6)。手机即使连进来,中继也会以 Connection refused 断开。
现与同文件的 UDP 中继保持一致,统一使用getLoopbackAddress()。 -
CI 精简并接入 GitHub Releases。
移除 check job(单测 + 五个模块 lint + 三个 assembleDebug),只构建平台签名 release 包;
构建成功后自动发布到 GitHub Releases,tag 为v0.2.12-<run_number>,附 APK。 -
移植「方控学习」(来自零跑线
DiPlay-main2.0)。
设置页新增独立区块,可把车上的按键或车机方控广播逐条绑定到 CarPlay 的五个媒体动作;
只转发学习过的键,顺带免疫车机内部命令回环。底层WheelLearning.kt本就在本线,
缺的是设置入口,这次补上。 -
修好「在线更新」(原实现是死的)。
updateSection()建完控件后立刻把updateMessage/updateActionButton置空,
按钮的进度回写全部落空 —— 点「检查更新」没有任何反应。已去掉这两行赋值。- 安装对话框的「稍后」不清
updateBusy,点一次之后整个区块直到重进页面都是死的。已修。 AppUpdater硬编码的是零跑线的发版规则(tagv2.0-、asset-leapmotor.apk),
在本线永远找不到自己的包。已改为本线的v0.2.12-<run>+mobile-release.apk。- 两条线共用同一个仓库,
/releases/latest有一半概率指向零跑线的包。现在先读
releases.atom(两条线的 release 都在里面),只取v0.2.12-前缀里最大的构建号,
/releases/latest仅作兜底。 currentBuild()的正则要求(数字)闭合,而本线版本名是0.2.12(189-6f275b6c),
解析结果恒为 null → 检查更新会卡在「正在检查更新…」。已放宽为只认左括号。- 更新区块从「关于」页移到设置页顶层(零跑线也在这里),同时保证全局只有一份视图引用。
-
「回到原车」按钮改品牌。
CarPlay 应用列表里的车机图标由 BYD 改为零跑:DEFAULT_OEM_LABEL由"BYD"改为"零跑"
(并迁移一次已存的旧值),默认图标res/raw/ic_car_home.png换成零跑 logo。 -
iOS 27 视频车内播放:修好启用链(原先整条是关着的)。
门控本身(N 挡 →LeapmotorGearMonitor.videoAllowed()→VideoInCarGate)在本线是完整的,
但AirPlayConfig.videoInCar用的是上游 BYD 的开关
BydOutputSettings.videoWhileParkedActive(),而那个开关只存在于 BYD 车辆数据面板里,
在本车机上够不着:BydOutputSettings.available()要装 BYD 包或 fingerprint 含 BYD,
独立 HUD 探测还要 API 28+(本机 25),BydAmapAdapter找的是com.byd.amapservice。
结果/info永远不带videoPlaybackInfo、SETUP 不协商videoPlayback,
iPhone 压根不会把视频交给车机 —— 门控再对也没用。现改为与零跑线一致的无条件true。
注意「提供能力」≠「允许播放」:VideoInCar.allowed初值为 false,
仍由 N 挡轮询放开,收到任何档位数据前一律不放行。
验证点:airplay /info videoInCar=true ... videoPlaybackAllowed=...。 -
补上
CarPlayVideo.detach()。
本线移植时漏了这个函数,而零跑线在CarPlayHostActivity的重连与退出两处都会调它。
缺它的后果是:CarPlay 重连/退出后视频播放器会留在屏上指向一个已经不存在的会话,
且reply()继续往已关闭的 controller 发消息。现补上函数并在两处 teardown 调用。 -
有线 lwIP 灰屏:事件通道的中继地址族不一致(上一版第 2 条的残留半截)。
上一版把中继侧改成了InetAddress.getLoopbackAddress()(本机解析为::1),
但监听侧仍绑着字面量127.0.0.1(AirPlaySession里的LOOPBACK常量)。
于是主控口 7000 通(它两边都用getLoopbackAddress()),
而eventPort/ timing / keepalive 三条全部Connection refused:18:02:55.588 wired lwip proxy accepted port=44738 fd=5 ← 手机连进来了 18:02:55.598 wired lwip relay ended: Connection refused ← 10ms 后中继失败 (整场没有 airplay event connection accepted)事件通道建不起来 →
sendCommand恒返回 false → 每秒一行
Video recovery: requested keyframe sent=false→ 视频 backlog 超 250ms 后等不到关键帧
→shown=0.0fps→ 灰屏。
现三处监听统一走新的listenerBindAddress(),与中继同一个调用;
UDP 中继的目标地址也从硬编码 IPv4 改成getLoopbackAddress()。Android 16 的报告独立印证了这条(报告 876,同一份 APK 的 lwIP 会话):
20:28:05.402 wired lwip proxy accepted port=37591 fd=5 20:28:05.406 wired lwip relay ended: failed to connect to ip6-localhost/[ip] (port 37591) ... connect failed: ECONNREFUSEDip6-localhost就是::1—— 中继拨 IPv6 环回、监听绑 IPv4 字面量,一眼可见。
同一份报告切到 VPN 通路后airplay event connection accepted立刻正常,
也说明问题只出在环回这一处,不在事件通道本身。 -
VPN 通路
Invalid argument:API 25 专有的平台拒绝,本版自证 + 自愈。
同一份 APK 在 Android 16 上 VPN 通路是通的(报告 876:wired VPN service bound之后
48ms 就attach result=started,随后airplay event connection accepted、
Video: first frame rendered)。所以这不是配置写错,是 Android 7 的平台拒收,
而它只回一个裸 EINVAL —— 既不说哪一步,也不说哪个参数。之前查到这里只能靠
adb logcat -s xcertplay-usb抓stage=。
本版把这条路做成自己会说话:establish()按full→no-allow-family→minimal三种配置依次尝试,
每次结果(接受/拒绝 + 异常类型 + message)都写进报告:
vpn tun established variant=.../vpn establish variant=... rejected ...。
哪一档被接受,说明被拒的就是它比上一档多出来的那个开关;一档被接受,有线通路当轮就活了。attach failed stage=...现在进报告(Log.w只进 logcat,报告只收onDiagnostic)。airplay listener ready现在带bind=<地址>,一眼看出监听到底绑在fe80::2还是::。
同时保留一处独立修正:监听地址是 link-local IPv6 时改用
::通配符
(裸 link-local 字面量scope_id为 0,bind()会 EINVAL;零跑线一直绑::所以没这问题)。 -
方控不压制原厂音乐:音频归属在 API 25 上从来没被申请过。
本车机把方向盘键交给「持有音频焦点的那个媒体会话」,所以必须由 DiPlay 拿到焦点 +
激活 MediaSession,原厂播放器才会让位。而本线的归属只由onMediaAudioChanged(true)
触发,那要 iPhone 把音乐流发过来;实测 iPhone 把音频留在车机蓝牙链路上
(整份报告 0 行Audio:,SETUP 只协商了屏幕流type=110),于是永远不触发。
另一条路onIphonePlaying(iPhone 报播放,走 CarPlay 的 now-playing,是通的)
却指向regainFocusLocked()—— 那个函数在 API 25 上是彻底的死代码:
focusRequest只在start()里、且只在 SDK ≥ 26 时才赋值,函数本身也直接
if (SDK_INT < O) return。结果焦点和 MediaSession 都没建立,方控两边都收,两边都切歌。
现让onIphonePlaying走与零跑线一致的updateLocked(playing)(会建会话、拿焦点)。
顺带:CarPlayMediaKeys的诊断行以前只进 logcat(DiPlay-MediaKeys这个 TAG),
报告里完全看不到,所以「焦点到底拿到没有」一直无从判断;现接进报告。- 媒体会话路径的按键改为经
LeapmotorMediaKeys.dispatch转发,与广播路径共用
去重窗口 —— 否则拿到焦点后一次按键会在两条路上各转一次,变成跳两首。
去重窗口的键也从「原始 action」改成「CarPlay 按钮」,
因为同一按在两条路上的名字不同(广播叫nextOne,媒体会话叫next)。 - 按 §58,没有恢复「发 pause 广播压制原厂播放器」那一招(车机会回声导致 CarPlay 自己被暂停)。
-
有线会话 15~63 秒必断:NCM 的「短包填充字节」被当成致命错误。
NcmUsbBridge.drainFrames()原本按wBlockLength % 512 == 0认定这一块后面必须跟一个
0x00 填充字节,否则failSession("Invalid NTB16 short-packet pad")—— 而failSession
会把整个 NCM 桥标记为DeviceUnavailable,于是整个 AirPlay 会话被拆掉重连。
问题在于填充字节属于 USB 传输,不属于 NTB 块:主机只在一个传输的长度正好是端点
maxPacketSize 整数倍时才补一个 0x00,而这里的接收缓冲会把同一个传输里的多块拼在一起,
于是「512 对齐的块」后面紧跟的其实是下一块的开头(NTB16 头以'N'开头,非 0)→ 误判。
192 报告里每一次断开(lwIP 与 VPN 都是)就是这一行,间隔 15s / 3s / 40s / 63s / 32s。
现改为:512 对齐时,后面那个字节是 0 就当作填充跳掉,不是 0 就不动它(留给下一轮解析)。 -
lwIP 断线后重建报 EBUSY,只能手动切模式才恢复。
LwipNative.start()在旧的原生栈没释放时抛USB IPv6 socket error errno=16(EBUSY,
文案在libdiplay_lwip.so里)。而attachLwip()重建会话前没有关闭上一个
LwipSessionNetwork—— 只在 attach 失败时才关,会话正常结束的路径不会关。
192 报告 21:36:55 与 21:37:17 两次 EBUSY,此后 lwIP 一直是死的,直到手动切到 VPN。
现改为在attachLwip()建新会话前先关掉旧的那个。 -
(回归修复)外置 Wi-Fi 被我改坏了,本版修回。
193 把「link-local IPv6 →::通配符」的替换套在了startAirPlayServer的多地址分支上。
bindAll要求一组地址绑在同一个端口,而::会覆盖同组里的其他地址
(无线主机地址里必然带一个 link-local IPv6)→ 每个候选端口都 EADDRINUSE →
BindException("No common AirPlay port available for the selected interface addresses")。
402 报告里每一次无线尝试都是这一句,HotspotReady之后 70ms 就拆。
现只在单地址分支(即 VPN 通路,地址就是fe80::2)做替换,多地址分支恢复原样。 -
有线默认改成 VPN/NCM,lwIP 改为手动开启。
DiPlayPreferences.wiredLwip默认由true改为false。老版本默认开着,
升级后会保留旧选择,所以加了一次性迁移(wired_lwip_defaulted_v2标记):
首次读取时写回false,之后设置里的开关...
DiPlay 0.2.12(202)· PSA
DiPlay 0.2.12 · PSA 车机版
Android 7.1.2(API 25)/ Qualcomm msm8953 车机上的 CarPlay 接收端。
本页是当前整体状态页,每次发布前更新。
✅ 可用
- 有线 CarPlay(VPN/NCM 通路):设备发现、重枚举、配对、NCM 数据通路、MFi 认证、
0x4300/0x4301、出画面(Video: first frame rendered)、音频(audioType=media)全部正常。
会话稳定性与断线重连已修(第 12、13 条)。 - 无线 CarPlay:
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
恢复 arm64-v8a 后恢复(第 17 条)。 - 外置 Wi-Fi / 同一局域网:✅ 可用。
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
- 界面汉化、零跑档位识别、倒车暂停、iOS 27 视频车内播放(N 挡门控)。
- 方向盘方控:S01 走通道 A(
car.meter.music.BROADCAST的 JSON)—— 实测正确,
切歌时原厂同步切并被立刻暂停、CarPlay 保持播放。 - T03 方控:本版补上通道 B(
com.leapmotor.customkey.music.pauseplay的整数 extras
ICU_MediaSwitch/ICU_MediaKey),并把车机总线接收器改为常开(第 21 条)。待 T03 复测。 - USB 权限弹窗:
MANAGE_USB+ 无障碍按内容判定,实测已通过
(usb auto-confirm: confirmed,S01)。 - 方控音频归属:焦点与 MediaSession 已拿到(
media keys active focusGranted=true session=true)。 - 在线更新(设置 → 在线更新):检查 GitHub 上的新构建 → 自动下载(支持直连 / 代理)→ 静默安装。
- CarPlay 应用列表里那个「回到原车」的图标按钮,名称与图标都是零跑(不再是 BYD)。
🔴 暂不可用 / 待确认
| 症状 | 现状 | 待办 |
|---|---|---|
| T03 方控无反应 | 根因已定位:通道 B 的解析缺失,且注册通道 B 的接收器此前只由默认关闭的调试开关创建 → 真机上从未注册(第 21 条) | 本版已修,待 T03 复测。请把日志发来,按下面「方控复测判据」核对 |
| lwIP 有线通路 | 出画面但没声音、手动断开后要重启应用、整体不如 VPN 稳 | 开关已摘除(第 18 条),代码留着但用不到 |
方控复测判据(S01 / T03 都适用,缺哪条就说明那步没走通):
- 起会话时应看到
car bus listening on 11 actions (wheel channels included)
—— 这是本版新增的,没有它 = 车机总线接收器没起来(第 21 条的根因)。 - 再看到
media keys listening on 11 car actions。 - 按一次方向盘的「下一首」,应出现恰好一行:
media key source=<通道> action=nextOne -> CarPlay 4 sent=true
(T03 的通道名应是com.leapmotor.customkey.music.pauseplay)。 - T03 若仍无反应:看有没有
media key extra-channel unmatched action=... extras=[...]
—— 这行会把该车机真正发的 extras 全量列出来,据此再加映射即可,不用再跑一趟实车。
若报告里一行 media key ... 都没有:说明按键根本没到 DiPlay,
要查的是车机把方控交给谁(原厂 / 蓝牙媒体通路),不是按键转发。
复测时报告里应出现(缺哪条就说明对应那步没走通):
- lwIP:
airplay event connection accepted from ...→airplay video event ready→
Video recovery: requested keyframe sent=true→Video: first frame rendered - VPN:成功则
vpn tun established variant=... address=fe80::2且
airplay listener ready family=IPv6 port=7000 bind=...;失败则attach failed stage=...
加每个vpn establish variant=... rejected ... - 稳定性:整场不再出现
Invalid NTB16 short-packet pad;AirPlay session ended只在
真正拔线时出现 - 方控:
media keys active focusGranted=true session=true,之后每次按键一行
media key source=... action=... -> CarPlay ... sent=true
⚠️ 已知限制
- CI 不再跑 lint 与单元测试,只出 release 包(原先的 check job 太慢)。
这意味着 lint 这道「防止 API 26+ 调用混进 API 25 构建」的自动防线没有了,
改运行时代码时请手动跑一次python D:\Launcher\kotlin_static_check.py <改动的 .kt 文件>。 - APK 必须带
arm64-v8a(第 17 条):为某个 native 库钉 ABI 会把整个进程变成 32 位,
而 32 位会打挂车载热点。CI 里已加断言。 - 有线走 VPN/NCM;lwIP 的设置开关已摘除(第 18 条),代码保留但在 arm64 进程里用不了。
- 开无线 CarPlay 时车机自身没有网络(msm8953 单射频,不支持 STA+GO 并发)。
本版改动(0.2.12)
-
有线 lwIP:地址通告改用真实地址。
原来把常量fe80::2作为有线端点地址,通过 iAP20x4301 carplay-start-session
下发给 iPhone;而 lwIP 接口上的 link-local 地址是启动时按 EUI-64 从网卡 MAC 推导的,
两者不同 → 手机邻居发现(NDP)解析失败 → 永远不发起 AirPlay TCP。
现改为读取 lwIP 自己的真实 link-local 地址。
(VPN 通路不受影响:它的fe80::2是真的被加到 TUN 接口上的。) -
有线 lwIP:回环中继地址族修正。
中继原来硬编码java.net.Socket("127.0.0.1", ...)(IPv4),而 AirPlay 监听绑定在
getLoopbackAddress()=::1(IPv6)。手机即使连进来,中继也会以 Connection refused 断开。
现与同文件的 UDP 中继保持一致,统一使用getLoopbackAddress()。 -
CI 精简并接入 GitHub Releases。
移除 check job(单测 + 五个模块 lint + 三个 assembleDebug),只构建平台签名 release 包;
构建成功后自动发布到 GitHub Releases,tag 为v0.2.12-<run_number>,附 APK。 -
移植「方控学习」(来自零跑线
DiPlay-main2.0)。
设置页新增独立区块,可把车上的按键或车机方控广播逐条绑定到 CarPlay 的五个媒体动作;
只转发学习过的键,顺带免疫车机内部命令回环。底层WheelLearning.kt本就在本线,
缺的是设置入口,这次补上。 -
修好「在线更新」(原实现是死的)。
updateSection()建完控件后立刻把updateMessage/updateActionButton置空,
按钮的进度回写全部落空 —— 点「检查更新」没有任何反应。已去掉这两行赋值。- 安装对话框的「稍后」不清
updateBusy,点一次之后整个区块直到重进页面都是死的。已修。 AppUpdater硬编码的是零跑线的发版规则(tagv2.0-、asset-leapmotor.apk),
在本线永远找不到自己的包。已改为本线的v0.2.12-<run>+mobile-release.apk。- 两条线共用同一个仓库,
/releases/latest有一半概率指向零跑线的包。现在先读
releases.atom(两条线的 release 都在里面),只取v0.2.12-前缀里最大的构建号,
/releases/latest仅作兜底。 currentBuild()的正则要求(数字)闭合,而本线版本名是0.2.12(189-6f275b6c),
解析结果恒为 null → 检查更新会卡在「正在检查更新…」。已放宽为只认左括号。- 更新区块从「关于」页移到设置页顶层(零跑线也在这里),同时保证全局只有一份视图引用。
-
「回到原车」按钮改品牌。
CarPlay 应用列表里的车机图标由 BYD 改为零跑:DEFAULT_OEM_LABEL由"BYD"改为"零跑"
(并迁移一次已存的旧值),默认图标res/raw/ic_car_home.png换成零跑 logo。 -
iOS 27 视频车内播放:修好启用链(原先整条是关着的)。
门控本身(N 挡 →LeapmotorGearMonitor.videoAllowed()→VideoInCarGate)在本线是完整的,
但AirPlayConfig.videoInCar用的是上游 BYD 的开关
BydOutputSettings.videoWhileParkedActive(),而那个开关只存在于 BYD 车辆数据面板里,
在本车机上够不着:BydOutputSettings.available()要装 BYD 包或 fingerprint 含 BYD,
独立 HUD 探测还要 API 28+(本机 25),BydAmapAdapter找的是com.byd.amapservice。
结果/info永远不带videoPlaybackInfo、SETUP 不协商videoPlayback,
iPhone 压根不会把视频交给车机 —— 门控再对也没用。现改为与零跑线一致的无条件true。
注意「提供能力」≠「允许播放」:VideoInCar.allowed初值为 false,
仍由 N 挡轮询放开,收到任何档位数据前一律不放行。
验证点:airplay /info videoInCar=true ... videoPlaybackAllowed=...。 -
补上
CarPlayVideo.detach()。
本线移植时漏了这个函数,而零跑线在CarPlayHostActivity的重连与退出两处都会调它。
缺它的后果是:CarPlay 重连/退出后视频播放器会留在屏上指向一个已经不存在的会话,
且reply()继续往已关闭的 controller 发消息。现补上函数并在两处 teardown 调用。 -
有线 lwIP 灰屏:事件通道的中继地址族不一致(上一版第 2 条的残留半截)。
上一版把中继侧改成了InetAddress.getLoopbackAddress()(本机解析为::1),
但监听侧仍绑着字面量127.0.0.1(AirPlaySession里的LOOPBACK常量)。
于是主控口 7000 通(它两边都用getLoopbackAddress()),
而eventPort/ timing / keepalive 三条全部Connection refused:18:02:55.588 wired lwip proxy accepted port=44738 fd=5 ← 手机连进来了 18:02:55.598 wired lwip relay ended: Connection refused ← 10ms 后中继失败 (整场没有 airplay event connection accepted)事件通道建不起来 →
sendCommand恒返回 false → 每秒一行
Video recovery: requested keyframe sent=false→ 视频 backlog 超 250ms 后等不到关键帧
→shown=0.0fps→ 灰屏。
现三处监听统一走新的listenerBindAddress(),与中继同一个调用;
UDP 中继的目标地址也从硬编码 IPv4 改成getLoopbackAddress()。Android 16 的报告独立印证了这条(报告 876,同一份 APK 的 lwIP 会话):
20:28:05.402 wired lwip proxy accepted port=37591 fd=5 20:28:05.406 wired lwip relay ended: failed to connect to ip6-localhost/[ip] (port 37591) ... connect failed: ECONNREFUSEDip6-localhost就是::1—— 中继拨 IPv6 环回、监听绑 IPv4 字面量,一眼可见。
同一份报告切到 VPN 通路后airplay event connection accepted立刻正常,
也说明问题只出在环回这一处,不在事件通道本身。 -
VPN 通路
Invalid argument:API 25 专有的平台拒绝,本版自证 + 自愈。
同一份 APK 在 Android 16 上 VPN 通路是通的(报告 876:wired VPN service bound之后
48ms 就attach result=started,随后airplay event connection accepted、
Video: first frame rendered)。所以这不是配置写错,是 Android 7 的平台拒收,
而它只回一个裸 EINVAL —— 既不说哪一步,也不说哪个参数。之前查到这里只能靠
adb logcat -s xcertplay-usb抓stage=。
本版把这条路做成自己会说话:establish()按full→no-allow-family→minimal三种配置依次尝试,
每次结果(接受/拒绝 + 异常类型 + message)都写进报告:
vpn tun established variant=.../vpn establish variant=... rejected ...。
哪一档被接受,说明被拒的就是它比上一档多出来的那个开关;一档被接受,有线通路当轮就活了。attach failed stage=...现在进报告(Log.w只进 logcat,报告只收onDiagnostic)。airplay listener ready现在带bind=<地址>,一眼看出监听到底绑在fe80::2还是::。
同时保留一处独立修正:监听地址是 link-local IPv6 时改用
::通配符
(裸 link-local 字面量scope_id为 0,bind()会 EINVAL;零跑线一直绑::所以没这问题)。 -
方控不压制原厂音乐:音频归属在 API 25 上从来没被申请过。
本车机把方向盘键交给「持有音频焦点的那个媒体会话」,所以必须由 DiPlay 拿到焦点 +
激活 MediaSession,原厂播放器才会让位。而本线的归属只由onMediaAudioChanged(true)
触发,那要 iPhone 把音乐流发过来;实测 iPhone 把音频留在车机蓝牙链路上
(整份报告 0 行Audio:,SETUP 只协商了屏幕流type=110),于是永远不触发。
另一条路onIphonePlaying(iPhone 报播放,走 CarPlay 的 now-playing,是通的)
却指向regainFocusLocked()—— 那个函数在 API 25 上是彻底的死代码:
focusRequest只在start()里、且只在 SDK ≥ 26 时才赋值,函数本身也直接
if (SDK_INT < O) return。结果焦点和 MediaSession 都没建立,方控两边都收,两边都切歌。
现让onIphonePlaying走与零跑线一致的updateLocked(playing)(会建会话、拿焦点)。
顺带:CarPlayMediaKeys的诊断行以前只进 logcat(DiPlay-MediaKeys这个 TAG),
报告里完全看不到,所以「焦点到底拿到没有」一直无从判断;现接进报告。- 媒体会话路径的按键改为经
LeapmotorMediaKeys.dispatch转发,与广播路径共用
去重窗口 —— 否则拿到焦点后一次按键会在两条路上各转一次,变成跳两首。
去重窗口的键也从「原始 action」改成「CarPlay 按钮」,
因为同一按在两条路上的名字不同(广播叫nextOne,媒体会话叫next)。 - 按 §58,没有恢复「发 pause 广播压制原厂播放器」那一招(车机会回声导致 CarPlay 自己被暂停)。
-
有线会话 15~63 秒必断:NCM 的「短包填充字节」被当成致命错误。
NcmUsbBridge.drainFrames()原本按wBlockLength % 512 == 0认定这一块后面必须跟一个
0x00 填充字节,否则failSession("Invalid NTB16 short-packet pad")—— 而failSession
会把整个 NCM 桥标记为DeviceUnavailable,于是整个 AirPlay 会话被拆掉重连。
问题在于填充字节属于 USB 传输,不属于 NTB 块:主机只在一个传输的长度正好是端点
maxPacketSize 整数倍时才补一个 0x00,而这里的接收缓冲会把同一个传输里的多块拼在一起,
于是「512 对齐的块」后面紧跟的其实是下一块的开头(NTB16 头以'N'开头,非 0)→ 误判。
192 报告里每一次断开(lwIP 与 VPN 都是)就是这一行,间隔 15s / 3s / 40s / 63s / 32s。
现改为:512 对齐时,后面那个字节是 0 就当作填充跳掉,不是 0 就不动它(留给下一轮解析)。 -
lwIP 断线后重建报 EBUSY,只能手动切模式才恢复。
LwipNative.start()在旧的原生栈没释放时抛USB IPv6 socket error errno=16(EBUSY,
文案在libdiplay_lwip.so里)。而attachLwip()重建会话前没有关闭上一个
LwipSessionNetwork—— 只在 attach 失败时才关,会话正常结束的路径不会关。
192 报告 21:36:55 与 21:37:17 两次 EBUSY,此后 lwIP 一直是死的,直到手动切到 VPN。
现改为在attachLwip()建新会话前先关掉旧的那个。 -
(回归修复)外置 Wi-Fi 被我改坏了,本版修回。
193 把「link-local IPv6 →::通配符」的替换套在了startAirPlayServer的多地址分支上。
bindAll要求一组地址绑在同一个端口,而::会覆盖同组里的其他地址
(无线主机地址里必然带一个 link-local IPv6)→ 每个候选端口都 EADDRINUSE →
BindException("No common AirPlay port available for the selected interface addresses")。
402 报告里每一次无线尝试都是这一句,HotspotReady之后 70ms 就拆。
现只在单地址分支(即 VPN 通路,地址就是fe80::2)做替换,多地址分支恢复原样。 -
有线默认改成 VPN/NCM,lwIP 改为手动开启。
DiPlayPreferences.wiredLwip默认由true改为false。老版本默认开着,
升级后会保留旧选择,所以加了一次性迁移(wired_lwip_defaulted_v2标记):
首次读取时写回false,之后设置里的开关...
DiPlay 0.2.12(201)· PSA
DiPlay 0.2.12 · PSA 车机版
Android 7.1.2(API 25)/ Qualcomm msm8953 车机上的 CarPlay 接收端。
本页是当前整体状态页,每次发布前更新。
✅ 可用
- 有线 CarPlay(VPN/NCM 通路):设备发现、重枚举、配对、NCM 数据通路、MFi 认证、
0x4300/0x4301、出画面(Video: first frame rendered)、音频(audioType=media)全部正常。
会话稳定性与断线重连已修(第 12、13 条)。 - 无线 CarPlay:
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
恢复 arm64-v8a 后恢复(第 17 条)。 - 外置 Wi-Fi / 同一局域网:✅ 可用。
- 车载自建热点:✅ 可用。178 版可用 → 之后为 lwIP 把 APK 钉成 32 位把它打挂 →
- 界面汉化、零跑档位识别、方向盘按键、倒车暂停、iOS 27 视频车内播放(N 挡门控)。
- 方控音频归属:焦点与 MediaSession 已拿到(
media keys active focusGranted=true session=true)。 - 在线更新(设置 → 在线更新):检查 GitHub 上的新构建 → 自动下载(支持直连 / 代理)→ 静默安装。
- CarPlay 应用列表里那个「回到原车」的图标按钮,名称与图标都是零跑(不再是 BYD)。
🔴 暂不可用 / 待确认
| 症状 | 现状 | 待办 |
|---|---|---|
| 方控切歌时原厂也切歌并同时播放 | 已对齐零跑线的夺权状态机(第 20 条) | 复测:看 audio focus change=-1 后有没有重新 request granted=true |
| USB 权限弹窗每次插拔都要手点 | 无障碍服务只在 AOSP 弹窗上生效,本车机弹窗来自别处 → 静默 | 本版加 MANAGE_USB + 放宽判定(第 19 条),复测 |
| lwIP 有线通路 | 出画面但没声音、手动断开后要重启应用、整体不如 VPN 稳 | 开关已摘除(第 18 条),代码留着但用不到 |
方控这一条为什么要日志:media key ...(广播与媒体会话两条路都会记)一次都没出现,
而音频是走 CarPlay 的。若下次日志仍然是「按了键但 DiPlay 一行都没有,CarPlay 却切了歌」,
那就说明按键根本没经过 DiPlay —— 最可能是车机把方向盘键交给原厂/蓝牙媒体通路,
再由 AVRCP 通知 iPhone 切歌(同一个 iPhone,所以 CarPlay 界面也跟着变,声音还从蓝牙出来
一遍)。那种情况下要修的是手机的车机蓝牙音频链路(零跑线用 BluetoothAudioHandoff
断开 A2DP/HFP 档位解决),不是按键转发。
复测时报告里应出现(缺哪条就说明对应那步没走通):
- lwIP:
airplay event connection accepted from ...→airplay video event ready→
Video recovery: requested keyframe sent=true→Video: first frame rendered - VPN:成功则
vpn tun established variant=... address=fe80::2且
airplay listener ready family=IPv6 port=7000 bind=...;失败则attach failed stage=...
加每个vpn establish variant=... rejected ... - 稳定性:整场不再出现
Invalid NTB16 short-packet pad;AirPlay session ended只在
真正拔线时出现 - 方控:
media keys active focusGranted=true session=true,之后每次按键一行
media key source=... action=... -> CarPlay ... sent=true
⚠️ 已知限制
- CI 不再跑 lint 与单元测试,只出 release 包(原先的 check job 太慢)。
这意味着 lint 这道「防止 API 26+ 调用混进 API 25 构建」的自动防线没有了,
改运行时代码时请手动跑一次python D:\Launcher\kotlin_static_check.py <改动的 .kt 文件>。 - APK 必须带
arm64-v8a(第 17 条):为某个 native 库钉 ABI 会把整个进程变成 32 位,
而 32 位会打挂车载热点。CI 里已加断言。 - 有线走 VPN/NCM;lwIP 的设置开关已摘除(第 18 条),代码保留但在 arm64 进程里用不了。
- 开无线 CarPlay 时车机自身没有网络(msm8953 单射频,不支持 STA+GO 并发)。
本版改动(0.2.12)
-
有线 lwIP:地址通告改用真实地址。
原来把常量fe80::2作为有线端点地址,通过 iAP20x4301 carplay-start-session
下发给 iPhone;而 lwIP 接口上的 link-local 地址是启动时按 EUI-64 从网卡 MAC 推导的,
两者不同 → 手机邻居发现(NDP)解析失败 → 永远不发起 AirPlay TCP。
现改为读取 lwIP 自己的真实 link-local 地址。
(VPN 通路不受影响:它的fe80::2是真的被加到 TUN 接口上的。) -
有线 lwIP:回环中继地址族修正。
中继原来硬编码java.net.Socket("127.0.0.1", ...)(IPv4),而 AirPlay 监听绑定在
getLoopbackAddress()=::1(IPv6)。手机即使连进来,中继也会以 Connection refused 断开。
现与同文件的 UDP 中继保持一致,统一使用getLoopbackAddress()。 -
CI 精简并接入 GitHub Releases。
移除 check job(单测 + 五个模块 lint + 三个 assembleDebug),只构建平台签名 release 包;
构建成功后自动发布到 GitHub Releases,tag 为v0.2.12-<run_number>,附 APK。 -
移植「方控学习」(来自零跑线
DiPlay-main2.0)。
设置页新增独立区块,可把车上的按键或车机方控广播逐条绑定到 CarPlay 的五个媒体动作;
只转发学习过的键,顺带免疫车机内部命令回环。底层WheelLearning.kt本就在本线,
缺的是设置入口,这次补上。 -
修好「在线更新」(原实现是死的)。
updateSection()建完控件后立刻把updateMessage/updateActionButton置空,
按钮的进度回写全部落空 —— 点「检查更新」没有任何反应。已去掉这两行赋值。- 安装对话框的「稍后」不清
updateBusy,点一次之后整个区块直到重进页面都是死的。已修。 AppUpdater硬编码的是零跑线的发版规则(tagv2.0-、asset-leapmotor.apk),
在本线永远找不到自己的包。已改为本线的v0.2.12-<run>+mobile-release.apk。- 两条线共用同一个仓库,
/releases/latest有一半概率指向零跑线的包。现在先读
releases.atom(两条线的 release 都在里面),只取v0.2.12-前缀里最大的构建号,
/releases/latest仅作兜底。 currentBuild()的正则要求(数字)闭合,而本线版本名是0.2.12(189-6f275b6c),
解析结果恒为 null → 检查更新会卡在「正在检查更新…」。已放宽为只认左括号。- 更新区块从「关于」页移到设置页顶层(零跑线也在这里),同时保证全局只有一份视图引用。
-
「回到原车」按钮改品牌。
CarPlay 应用列表里的车机图标由 BYD 改为零跑:DEFAULT_OEM_LABEL由"BYD"改为"零跑"
(并迁移一次已存的旧值),默认图标res/raw/ic_car_home.png换成零跑 logo。 -
iOS 27 视频车内播放:修好启用链(原先整条是关着的)。
门控本身(N 挡 →LeapmotorGearMonitor.videoAllowed()→VideoInCarGate)在本线是完整的,
但AirPlayConfig.videoInCar用的是上游 BYD 的开关
BydOutputSettings.videoWhileParkedActive(),而那个开关只存在于 BYD 车辆数据面板里,
在本车机上够不着:BydOutputSettings.available()要装 BYD 包或 fingerprint 含 BYD,
独立 HUD 探测还要 API 28+(本机 25),BydAmapAdapter找的是com.byd.amapservice。
结果/info永远不带videoPlaybackInfo、SETUP 不协商videoPlayback,
iPhone 压根不会把视频交给车机 —— 门控再对也没用。现改为与零跑线一致的无条件true。
注意「提供能力」≠「允许播放」:VideoInCar.allowed初值为 false,
仍由 N 挡轮询放开,收到任何档位数据前一律不放行。
验证点:airplay /info videoInCar=true ... videoPlaybackAllowed=...。 -
补上
CarPlayVideo.detach()。
本线移植时漏了这个函数,而零跑线在CarPlayHostActivity的重连与退出两处都会调它。
缺它的后果是:CarPlay 重连/退出后视频播放器会留在屏上指向一个已经不存在的会话,
且reply()继续往已关闭的 controller 发消息。现补上函数并在两处 teardown 调用。 -
有线 lwIP 灰屏:事件通道的中继地址族不一致(上一版第 2 条的残留半截)。
上一版把中继侧改成了InetAddress.getLoopbackAddress()(本机解析为::1),
但监听侧仍绑着字面量127.0.0.1(AirPlaySession里的LOOPBACK常量)。
于是主控口 7000 通(它两边都用getLoopbackAddress()),
而eventPort/ timing / keepalive 三条全部Connection refused:18:02:55.588 wired lwip proxy accepted port=44738 fd=5 ← 手机连进来了 18:02:55.598 wired lwip relay ended: Connection refused ← 10ms 后中继失败 (整场没有 airplay event connection accepted)事件通道建不起来 →
sendCommand恒返回 false → 每秒一行
Video recovery: requested keyframe sent=false→ 视频 backlog 超 250ms 后等不到关键帧
→shown=0.0fps→ 灰屏。
现三处监听统一走新的listenerBindAddress(),与中继同一个调用;
UDP 中继的目标地址也从硬编码 IPv4 改成getLoopbackAddress()。Android 16 的报告独立印证了这条(报告 876,同一份 APK 的 lwIP 会话):
20:28:05.402 wired lwip proxy accepted port=37591 fd=5 20:28:05.406 wired lwip relay ended: failed to connect to ip6-localhost/[ip] (port 37591) ... connect failed: ECONNREFUSEDip6-localhost就是::1—— 中继拨 IPv6 环回、监听绑 IPv4 字面量,一眼可见。
同一份报告切到 VPN 通路后airplay event connection accepted立刻正常,
也说明问题只出在环回这一处,不在事件通道本身。 -
VPN 通路
Invalid argument:API 25 专有的平台拒绝,本版自证 + 自愈。
同一份 APK 在 Android 16 上 VPN 通路是通的(报告 876:wired VPN service bound之后
48ms 就attach result=started,随后airplay event connection accepted、
Video: first frame rendered)。所以这不是配置写错,是 Android 7 的平台拒收,
而它只回一个裸 EINVAL —— 既不说哪一步,也不说哪个参数。之前查到这里只能靠
adb logcat -s xcertplay-usb抓stage=。
本版把这条路做成自己会说话:establish()按full→no-allow-family→minimal三种配置依次尝试,
每次结果(接受/拒绝 + 异常类型 + message)都写进报告:
vpn tun established variant=.../vpn establish variant=... rejected ...。
哪一档被接受,说明被拒的就是它比上一档多出来的那个开关;一档被接受,有线通路当轮就活了。attach failed stage=...现在进报告(Log.w只进 logcat,报告只收onDiagnostic)。airplay listener ready现在带bind=<地址>,一眼看出监听到底绑在fe80::2还是::。
同时保留一处独立修正:监听地址是 link-local IPv6 时改用
::通配符
(裸 link-local 字面量scope_id为 0,bind()会 EINVAL;零跑线一直绑::所以没这问题)。 -
方控不压制原厂音乐:音频归属在 API 25 上从来没被申请过。
本车机把方向盘键交给「持有音频焦点的那个媒体会话」,所以必须由 DiPlay 拿到焦点 +
激活 MediaSession,原厂播放器才会让位。而本线的归属只由onMediaAudioChanged(true)
触发,那要 iPhone 把音乐流发过来;实测 iPhone 把音频留在车机蓝牙链路上
(整份报告 0 行Audio:,SETUP 只协商了屏幕流type=110),于是永远不触发。
另一条路onIphonePlaying(iPhone 报播放,走 CarPlay 的 now-playing,是通的)
却指向regainFocusLocked()—— 那个函数在 API 25 上是彻底的死代码:
focusRequest只在start()里、且只在 SDK ≥ 26 时才赋值,函数本身也直接
if (SDK_INT < O) return。结果焦点和 MediaSession 都没建立,方控两边都收,两边都切歌。
现让onIphonePlaying走与零跑线一致的updateLocked(playing)(会建会话、拿焦点)。
顺带:CarPlayMediaKeys的诊断行以前只进 logcat(DiPlay-MediaKeys这个 TAG),
报告里完全看不到,所以「焦点到底拿到没有」一直无从判断;现接进报告。- 媒体会话路径的按键改为经
LeapmotorMediaKeys.dispatch转发,与广播路径共用
去重窗口 —— 否则拿到焦点后一次按键会在两条路上各转一次,变成跳两首。
去重窗口的键也从「原始 action」改成「CarPlay 按钮」,
因为同一按在两条路上的名字不同(广播叫nextOne,媒体会话叫next)。 - 按 §58,没有恢复「发 pause 广播压制原厂播放器」那一招(车机会回声导致 CarPlay 自己被暂停)。
-
有线会话 15~63 秒必断:NCM 的「短包填充字节」被当成致命错误。
NcmUsbBridge.drainFrames()原本按wBlockLength % 512 == 0认定这一块后面必须跟一个
0x00 填充字节,否则failSession("Invalid NTB16 short-packet pad")—— 而failSession
会把整个 NCM 桥标记为DeviceUnavailable,于是整个 AirPlay 会话被拆掉重连。
问题在于填充字节属于 USB 传输,不属于 NTB 块:主机只在一个传输的长度正好是端点
maxPacketSize 整数倍时才补一个 0x00,而这里的接收缓冲会把同一个传输里的多块拼在一起,
于是「512 对齐的块」后面紧跟的其实是下一块的开头(NTB16 头以'N'开头,非 0)→ 误判。
192 报告里每一次断开(lwIP 与 VPN 都是)就是这一行,间隔 15s / 3s / 40s / 63s / 32s。
现改为:512 对齐时,后面那个字节是 0 就当作填充跳掉,不是 0 就不动它(留给下一轮解析)。 -
lwIP 断线后重建报 EBUSY,只能手动切模式才恢复。
LwipNative.start()在旧的原生栈没释放时抛USB IPv6 socket error errno=16(EBUSY,
文案在libdiplay_lwip.so里)。而attachLwip()重建会话前没有关闭上一个
LwipSessionNetwork—— 只在 attach 失败时才关,会话正常结束的路径不会关。
192 报告 21:36:55 与 21:37:17 两次 EBUSY,此后 lwIP 一直是死的,直到手动切到 VPN。
现改为在attachLwip()建新会话前先关掉旧的那个。 -
(回归修复)外置 Wi-Fi 被我改坏了,本版修回。
193 把「link-local IPv6 →::通配符」的替换套在了startAirPlayServer的多地址分支上。
bindAll要求一组地址绑在同一个端口,而::会覆盖同组里的其他地址
(无线主机地址里必然带一个 link-local IPv6)→ 每个候选端口都 EADDRINUSE →
BindException("No common AirPlay port available for the selected interface addresses")。
402 报告里每一次无线尝试都是这一句,HotspotReady之后 70ms 就拆。
现只在单地址分支(即 VPN 通路,地址就是fe80::2)做替换,多地址分支恢复原样。 -
有线默认改成 VPN/NCM,lwIP 改为手动开启。
DiPlayPreferences.wiredLwip默认由true改为false。老版本默认开着,
升级后会保留旧选择,所以加了一次性迁移(wired_lwip_defaulted_v2标记):
首次读取时写回false,之后设置里的开关仍然可以覆盖。
lwIP 模式目前的已知缺陷:出画面但没声音、手动断开后必须重启应用才能重连、整体不如
VPN 稳 —— 所以默认不用它。 -
NCM 短包填充的修法对齐零跑线。
第 12 条那处逻辑与零跑线NcmUsbBridge已有实现逐字一致(零跑线早就修过,
PSA 这份是移植时漏了那次修复),并补上零跑线的注释与一次性诊断行
(NTB16 block without the expected pad byte; accepting a ZLP terminator)。
注释里写明了机制:Apple 用单个 0x00 填充让传输以短包结束,而 Android 7 会把它变成
ZLP,readChunk()会丢掉 ZLP —— 所以那个字节常常根本不存在。 -
恢复 APK 的 ABI(arm64-v8a 回来了),lwIP 在本机因此不可用。
178 之后为了 lwIP 那个 v7a-only 的libdiplay_lwip.so,把 APK 钉成只有armeabi-v7a
(`shared/b...