首页 / 资讯中心 / 文章详情

DiPlay 侧边面板实现解析:让 CarPlay 与车载信息分屏共享主屏幕

DiPlay 侧边面板实现解析:让 CarPlay 与车载信息分屏共享主屏幕 ★ FEATURED ARTICLE
移动开发智能硬件音视频【免费下载链接】DiPlayIndependent CarPlay receiver for compatible Android head units. Wired and wireless public preview.项目地址https://gitcode.com/gh_mirrors/di/DiPlay点击查看免费下载DiPlay 的「CarPlay 旁侧边面板」Side panel实验性功能让 iPhone 把 CarPlay 画在主屏幕的三分之二而 DiPlay 在剩下的三分之一里显示自己的 Android 面板——横屏时位于乘客一侧竖屏时位于屏幕底部且随时可切回全屏 CarPlay 而无需重连。本文以仓库文档 docs/SIDE_PANEL.md 为主线结合 CarPlayViewAreas.kt 与 CarPlayHostActivity.kt 的源码实现讲清这一功能的设置方式、view area 原理、面板的布局与刷新机制以及同一轮研究中验证的 iAP2 协议边界。读完你可以掌握如何在 DiPlay 中开启并切换该功能、它依赖的updateViewArea调用链以及 CarPlay 流上叠放自定义 Android 视图的通用做法。功能概览与开启方式功能的行为在文档中描述得很直接CarPlay 与 DiPlay 共享车机主屏iPhone 在屏幕的三分之二内绘制 CarPlayDiPlay 在剩余区域显示自己的面板。具体布局规则为横屏CarPlay 保持位于驾驶员一侧面板占据乘客一侧的三分之一——左舵车在右侧开启Right-hand drive右舵后换到左侧竖屏面板是屏幕底部的一条横幅band车机可以随时在这两种形态面板/全屏 CarPlay之间切换无需重连功能默认关闭入口在Settings → Display and performance → Side panel next to CarPlay开启后会话内的菜单会出现Side panel按钮面板自身则有一个Full screen按钮用于切回全屏。从字符串资源 strings.xml 可以看到该功能的官方描述含实验性标注与下次连接时生效的适用前提string nameside_panelSide panel next to CarPlay (experimental)/string string nameside_panel_descriptionCarPlay can move to two thirds of the screen while DiPlay shows a panel in the rest: on the passengers side of a landscape screen, at the bottom of a portrait one (clock and, where it is read, the battery). Show and hide it from the in-session menu. Applies at the next connection./string string nameside_panel_showSide panel/string string nameside_panel_full_screen⤢ Full screen/string注意字符串中两个关键前提面板显示时钟以及已读取到时的电量battery 一项依赖车机侧数据能否读到设置变更在下一次连接时生效。对应的持久化实现在 SidePanel.ktobject SidePanelSettings { private const val PREFS diplay_side_panel private const val KEY_ENABLED enabled fun enabled(context: Context): Boolean context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getBoolean(KEY_ENABLED, false) fun setEnabled(context: Context, enabled: Boolean) context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().putBoolean(KEY_ENABLED, enabled).apply() }即独立的 SharedPreferences 文件diplay_side_panel键enabled默认值false。会话建立时该开关被传入 view area 的构建参数CarPlayHostActivity.kt 第 3529–3541 行 中sidePanel SidePanelSettings.enabled(this)因此关闭状态下本次会话根本不会声明侧边面板区域也就没有对应的菜单按钮——这解释了下次连接生效的说法。工作原理基于 View Areas 的面积声明与切换该功能构建在 DiPlay 的 view areas 机制之上详见 docs/VIEW_AREAS.md。CarPlay accessory 可以在主屏视频流里声明多个 view area并告诉 iPhone 当前画在哪一个。开启侧边面板后主屏在每种屏幕方向、每个 dock 边缘下额外声明一个占屏幕三分之二大小的区域横屏取左侧或右舵时的右侧竖屏取顶部。源码侧的核心是 CarPlayViewAreas.kt。其中区域按用途分成三类SIDE_PANEL只在用户请求时才会被选中/** What an area is for; SIDE_PANEL is CarPlay beside DiPlays panel and is only chosen on request. */ enum class Kind { FULL_SCREEN, SPLIT_SCREEN, SIDE_PANEL }build()构造侧边面板区域时第 148–158 行if (sidePanel) for (screen in screens) { // CarPlay keeps two thirds: the drivers side of a landscape screen (the panel on the // passengers side) or the top of a portrait one (the panel at the bottom). val width if (screen.portrait) screen.width else (screen.width * 2 / 3) and 1.inv() val height if (screen.portrait) (screen.height * 2 / 3) and 1.inv() else screen.height val originX if (rightHandDrive) screen.width - width else 0 for (edge in edges) { areas AirPlayViewArea(width, height, originX originX, dockEdge edge) slots Slot(Kind.SIDE_PANEL, screen.portrait) } }要点横屏面积为屏幕宽的2/3向下取整到偶数and 1.inv()竖屏为高的2/3originX由rightHandDrive决定实现左舵/右舵镜像。index()/sidePanel()负责按当前方向 dock 边缘查回对应区域索引use()记录当前使用的区域整个类不依赖任何 Android 类型除偏好设置外因此可完整单元测试见 CarPlayViewAreasTest.kt。切换动作走 CarPlay 事件通道的updateViewArea。AirPlayInfoPlist.kt 第 170 行附近 记录了在 Tang 车 iOS 27 上实测到的消息形态iPhone 只有在带上了动画时长animationDurationMillis和相邻区域adjacentViewAreas时才响应切换。消息最终经 AirPlaySession.showViewArea() 发出编排层 CarPlayController.showViewArea() 透传。关键在于iPhone 只知道自己被要求用更小的区域对面板内容一无所知。因此 DiPlay 采取的做法是画布按整块屏幕布局layoutArea()对SIDE_PANEL类区域返回对应的全屏区域第 63–66 行保证流内容不变、iPhone 继续发送整帧普通 Android 视图盖住黑条panelRect()计算 CarPlay 留下的黑色条带流坐标第 72–84 行 分别处理横屏左右侧与竖屏底部三种情况触摸分区面板内触摸留在 DiPlay其余区域照旧交给 iPhone窗口变化收场屏幕转向、车机分屏等窗口变化会结束面板并按常规选择匹配区域关闭 DiPlay 或会话结束则隐藏面板并停止其刷新。面板的构建、定位与刷新源码走读面板内容时钟、电量与全屏按钮CarPlayHostActivity.kt 的buildSidePanel()第 1510–1539 行 构造了一个垂直LinearLayout包含三部分一个TextClock24 小时制HH:mm/ 12 小时制h:mm72sp 白字一个电量TextViewsidePanelBattery30sp一个Full screen按钮点击调用showSidePanel(false)切回全屏。面板背景为深色rgb(16,16,18)且isClickable true——这使落在面板上的触摸被其消费不再穿透到下方的手势层/CarPlay 流。定位把流坐标映射到视频布局iPhone 持续发送整帧而视频视图在窗口内可能被 letterbox黑边缩放。placeSidePanel()第 1542–1557 行把面板条带从流像素坐标换算到窗口坐标private fun placeSidePanel(viewWidth: Int, viewHeight: Int) { val panel sidePanel ?: return val display sessionDisplay ?: return val strip display.viewAreas?.let { it.panelRect(it.current) } ?: return val content contentRect(viewWidth, viewHeight) val scaleX content.width / display.width val scaleY content.height / display.height val left Math.round(content.left strip.originX * scaleX) val top Math.round(content.top strip.originY * scaleY) panel.layoutParams FrameLayout.LayoutParams(...).apply { // Stream coordinates are physical pixels, independent of the UIs reading direction. leftMargin left; topMargin top // Gravity.TOP or Gravity.LEFT } }两个值得注意的设计letterbox 跟随contentRect是视频内容在窗口内的实际矩形因此面板始终精确盖在流的黑条上而不是简单地贴窗口边缘物理坐标不受 RTL 影响Gravity.TOP or Gravity.LEFT 流像素坐标保证界面切到右起书写语言如阿拉伯语、希伯来语时面板不会翻到 CarPlay 上面去。这一点由测试 SidePanelLayoutTest.letterboxedPanelKeepsItsPhysicalPassengerSideInEitherLayoutDirection 覆盖2560×1440 流放入 1200×1200 窗口正方形 letterbox在 LTR/RTL × 左舵/右舵四种组合下面板left恒为 800左舵或 0右舵且尺寸一致400×675top263。另一个测试portraitPanelCoversTheBottomStripOfTheSquareStream验证了竖屏旋屏方形流下面板覆盖底部条带。显示、隐藏与状态恢复showSidePanel(show)第 1559–1574 行是切换入口显示时取areas.sidePanel(portrait)隐藏时取areas.indexFor(...)当前窗口形状匹配的全屏/分屏区域通过controller?.showViewArea(target)下发updateViewArea只有下发成功才更新状态sent为 false 时直接返回避免 UI 状态与实际区域不一致记录日志Side panel shown/hidden: view area $target sent$sent显示时启动周期刷新sidePanelTick隐藏/重置时移除回调最后updateVideoLayout(...)让画布按整屏重新布局。会话菜单中的按钮同样以区域是否存在为前提——sessionDisplay?.viewAreas?.sidePanel() ! null时才渲染Side panel / Full screen切换按钮第 1612–1621 行。状态收场时机源码中多处调用resetSidePanel()——新会话开始时不带侧边面板启动第 4060 行、屏幕转向或进入车机分屏时结束面板第 4246 行。窗口大小变化时若面板仍显示则重新placeSidePanel第 4327 行。电量刷新的条件refreshSidePanel()第 1582–1588 行只在 DiPlay 已为 iPhone 读取电量的场景BydOutputSettings.batteryToIphoneActive下才显示 xx% · xx km否则留空——即字符串资源中where it is read的实现。刷新周期为常量SIDE_PANEL_REFRESH_MILLIS 5_000L第 4941 行由sidePanelTick在主线程上自调度面板关闭即停止。会话接管adoption当新的宿主 Activity 接管一个面板已打开的存活会话时adoptBackgroundSession会恢复面板视图与刷新回调且不改变 view area区域索引current在CarPlayViewAreas内跨 Activity 共享。这一行为由 SidePanelLayoutTest.adoptingAnOpenSidePanelRestoresItsViewAndRefreshWithoutChangingTheArea 验证接管后areas.current仍指向侧边面板区域sidePanelShown为 true、面板可见、sidePanelTick已挂入 Handler。生命周期另有 SidePanelLifecycleTest.kt 覆盖。实测结果文档记录的实测环境2024 款 BYD TangDiLink 5.02560×1440 可旋转中控屏 iOS 27 iPhone无线 CarPlay同设计的实验室构建CarPlay 在横屏左侧 2/3与竖屏顶部 2/3之间移动并切回耗时不到一秒全程无需重连CarPlay 区域内的触摸全部正确命中面板显示时钟与车辆电量和续航里程一个 dock 相关发现声明了多个区域后CarPlay 在 Automatic dock 下会把 dock 放到屏幕底部需要通过 dock 设置来指定位置与 docs/VIEW_AREAS.md 中声明第二个区域后 Automatic dock 停靠底部的结论一致。演进方向从投屏到 CarPlay 外围的启动器由于面板就是一个普通 Android 视图它能显示 DiPlay 可读取或操作的任何东西。文档列出的方向包括经车机 adb 读取的车辆数据空调、胎压、车门状态对车辆的控制例如 BYD autoservice 中已记录的空调 fid小组件、BYD 应用快捷方式或其他地图源的导航图。再补上几块拼图——方向盘按键、驻车时自动显示面板的规则、按屏幕尺寸区分的布局、面板侧边选择——DiPlay 就能作为 CarPlay 外围的一个launcher而不仅仅是一块全屏投屏。这一方向与仓库中已存在的车辆数据桥接模块如 BydNavigationOutputs.kt、BydHudBridge.kt 等 hud 包实现在数据链路上是衔接的面板当前的电量读数正是复用了为 iPhone 供电量的同一数据源。同一轮研究的其他 iAP2 发现在同一台车、同一实验室构建上参考 Apple iAP2 消息规范Xcode CarPlay Simulator 的iap2messages-internal.i2mspecarchive仅作为数据读取还验证了以下边界VehicleStatus 附加项在 VehicleStatus 组件中声明OutsideTemperature、InsideTemperature、WiperStatus、BarometricPressure、Alerts与PassengerSeatStatus均被接受iPhone 随后订阅了车外温度、雨刮、气压、警报与乘客座椅状态唯独没有车内温度。从车端发送实时值车外温度、雨刮、双闪也被接受但 CarPlay 与 Apple Maps 界面上没有任何可见变化道路物体检测identification 参数 33供车辆摄像头报告路牌、车道与物体iPhone 拒绝了该 identification对 0x0021 返回IdentificationRejected推测保留给经过认证的车辆应用发现对所有 CarPlay 应用发起带图标的StartAppDiscoveryUpdatesiPhone 以AppDiscoveryUpdate应答CarPlayAppListAvailable 0Unknown且没有列表。这些结果说明CarPlay 侧对 accessory 的声明是选择性信任的——接受更多声明不等于这些能力会在 UI 中兑现而部分能力如道路物体检测则存在明确的准入限制。理解这一点有助于正确评估类似车端数据注入 CarPlay这类功能的实际边界。小结DiPlay 的侧边面板是一个以最小改动撬动最大效果为特点的实验性功能它没有发明新的传输能力而是复用 CarPlay 既有的多 view area 声明 updateViewArea无重连切换机制再用一个定位精确流坐标 → 视频布局映射、触摸隔离、5 秒周期刷新的 Android 视图填满 CarPlay 让出的黑色条带。设置入口在 Display and performance 页默认关闭、下次连接生效核心逻辑集中在 CarPlayViewAreas.kt区域计算、CarPlayHostActivity.kt面板构建/定位/刷新/收场与 SidePanel.kt开关持久化并有 SidePanelLayoutTest.kt、SidePanelLifecycleTest.kt、CarPlayViewAreasTest.kt 三组测试对定位数学、RTL 稳定性、会话接管与区域选择行为提供验证。若要在此基础上扩展面板内容车辆数据、控制、小组件面板就是一个普通的 Android 视图树可任意替换buildSidePanel()的构建逻辑而 view area 侧完全不用改动。赞分享移动开发智能硬件音视频【免费下载链接】DiPlayIndependent CarPlay receiver for compatible Android head units. Wired and wireless public preview.项目地址https://gitcode.com/gh_mirrors/di/DiPlay点击查看免费下载相关推荐Screego屏幕共享服务低延迟高清屏幕共享解决方案解析Screego屏幕共享服务低延迟高清屏幕共享解决方案解析 项目背景与定位 在现代远程协作环境中屏幕共享已成为日常工作不可或缺的功能。然而许多企业级聊天解决音视频后端VueUse 实战指南useDisplayMedia 实现响应式屏幕共享与录屏VueUse 实战指南useDisplayMedia 实现响应式屏幕共享与录屏 useDisplayMedia 是 VueUse 中基于 Vue 3 Comp前端MiroTalk P2P中的实时协作功能白板、文件共享与屏幕共享MiroTalk P2P中的实时协作功能白板、文件共享与屏幕共享 在当今远程协作的时代拥有强大实时协作功能的视频会议平台变得至关重要。MiroTalk P2后端前端音视频即时通讯上一篇Velox扩展开发教程如何自定义高性能数据处理组件下一篇10分钟上手openeuler/libvirt从安装到创建虚拟机的快速教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站