跳转至

米德宝 AIDL 音乐接口定义

最后更新:

迁移提示:EVCC 开放 API 1.1 已提供通用音乐、歌词、封面及播放控制。旧服务保留过渡期,字段和原授权规则不变;新接入请阅读 https://evcc.coauto.cc/docs/music/ 与 https://evcc.coauto.cc/docs/migration-midebao/ 。停用须完成客户端验收并另行公告。 适用版本:EVCC 3.1.3 起。AIDL 能力版本 3;音乐 JSON schemaVersion 为 1;原车辆属性快照仍为 2。

本文件可单独交给银河悬浮窗歌词开发者。音乐数据为独立通道,无需 EVCC 用户配置属性或变量。

1. 端点、开关与身份

项目 定义
EVCC 包名 com.kooo.evcc
Service 类 com.kooo.evcc.midebao.MidebaoLinkBridgeService
绑定 Action com.kooo.evcc.midebao.BIND
主 AIDL 描述符 com.kooo.evcc.midebao.IMidebaoLinkBridge
音乐回调描述符 com.kooo.evcc.midebao.IMidebaoMusicCallback
唯一接收应用 银河悬浮窗歌词 com.galaxy.lyricsnext,且安装签名通过 EVCC 既有米德宝签名校验
音乐共享条件 云端允许米德宝联动,并开启“米德宝联动”及“米德宝 AIDL 联动”
音乐来源 已运行的 EVCC 音乐转发模块;共享不会自行启动音乐转发

新的三个音乐方法始终校验 Binder 调用 UID、唯一包名和 APK 签名。普通旧版 AIDL 的开放读取和应用写入授权不授予音乐权限。已验证的银河在共享关闭时仍可调用音乐方法、注册回调,只能取得 enabled=false 的空状态。

原 getProtocolVersion() 的读取开关校验保留:首次在米德宝 AIDL 关闭时握手可能得到 SecurityException;客户端应等待用户开启后重试,不通过 EVCC 应用版本猜测接口能力。成功查询且版本至少为 3 后使用音乐方法。已经注册的音乐回调在关闭后收到清空状态,并在重新开启时自动恢复,无需反复注册。

银河 Manifest 需要 EVCC 的包可见性声明:

<queries>
    <package android:name="com.kooo.evcc" />
</queries>

绑定示例:

Intent intent = new Intent("com.kooo.evcc.midebao.BIND").setPackage("com.kooo.evcc");
context.bindService(intent, connection, Context.BIND_AUTO_CREATE);

2. 完整 AIDL 定义

以下三个文件置于客户端 src/main/aidl/com/kooo/evcc/midebao/,保留包名、方法顺序和类型。

IMidebaoLinkBridge.aidl:

package com.kooo.evcc.midebao;
import com.kooo.evcc.midebao.IMidebaoLinkCallback;
import com.kooo.evcc.midebao.IMidebaoMusicCallback;

interface IMidebaoLinkBridge {
    int getProtocolVersion();
    String getStatus();
    String getSnapshotJson();
    void registerCallback(IMidebaoLinkCallback callback);
    void unregisterCallback(IMidebaoLinkCallback callback);
    boolean writeProperty(int propertyId, int areaId, String value);
    String getMusicSnapshotJson();
    void registerMusicCallback(IMidebaoMusicCallback callback);
    void unregisterMusicCallback(IMidebaoMusicCallback callback);
}

IMidebaoLinkCallback.aidl,原车辆属性回调:

package com.kooo.evcc.midebao;
oneway interface IMidebaoLinkCallback {
    void onSnapshotChanged(String snapshotJson);
    void onStatusChanged(String status);
}

IMidebaoMusicCallback.aidl,独立音乐回调:

package com.kooo.evcc.midebao;
oneway interface IMidebaoMusicCallback {
    void onMusicStateChanged(String stateJson);
    void onLyricsChunk(String lyricsChunkJson);
}

主接口原六个方法保持 FIRST_CALL_TRANSACTION + 0..5。新增方法按上述顺序为 +6、+7、+8;音乐回调的两个方法分别为 +0、+1。主接口同步返回,音乐回调为 oneway;EVCC 不等待银河完成业务处理,没有逐帧 ACK 或持久化重放。

客户端发布时应保留这三个 AIDL 接口及内部类的名称。特别是启用 -adaptclassstrings 时,避免混淆改写 Binder 描述符;示例目录提供 consumer-rules.pro。

新方法 语义
getMusicSnapshotJson() 返回最新音乐状态 JSON,不包含整首歌词正文,不强制刷新播放器或启动音乐模块
registerMusicCallback(callback) 立即安排当前状态和已有完整歌词;同一 Binder 重复注册不增加订阅,重新发送当前内容
unregisterMusicCallback(callback) 取消该回调及其待发送任务;不会关闭 EVCC 音乐转发
onMusicStateChanged(stateJson) 一份完整的音乐状态,包含当前行和完整歌词版本信息
onLyricsChunk(lyricsChunkJson) 当前完整歌词的一片;收齐同版本所有片后替换完整歌词

3. 音乐状态 JSON

{
  "schemaVersion": 1,
  "streamId": "e806e643-c608-47d2-b29d-f78e317c1104",
  "sequence": 42,
  "trackId": "opaque-track-identifier",
  "enabled": true,
  "available": true,
  "playbackState": "playing",
  "title": "示例歌曲",
  "artist": "示例歌手",
  "album": "示例专辑",
  "sourcePackage": "com.example.music",
  "sourceAppName": "音乐",
  "positionMs": 12500,
  "positionAtElapsedMs": 800000,
  "durationMs": 210000,
  "playbackSpeed": 1.0,
  "currentLyric": "当前这一句歌词",
  "lineStartMs": 12000,
  "lineEndMs": 16000,
  "lyricOffsetMs": 0,
  "lyricsAvailable": true,
  "lyricsFormat": "lrc",
  "lyricsRevision": 3,
  "bpm": 120.0,
  "bpmStatus": "matched",
  "textTruncated": false
}
字段 类型 约定
schemaVersion int 当前为 1;与 AIDL 能力版本独立
streamId string EVCC 音乐发布器运行周期标识;进程重启后改变,普通开关切换不必改变
sequence long 同一个 stream 内递增;首次订阅、同步读取与回调可能交错,允许序号有间隔
trackId string 根据来源和歌曲元数据生成的不透明歌曲标识;客户端只做相等比较,无音乐时为空
enabled boolean 音乐共享是否开启
available boolean EVCC 音乐转发正在运行且存在音乐会话
playbackState string playing、paused_or_stopped、none;当前源不区分暂停与停止
title / artist / album string 歌名、歌手、专辑;缺少时为空
sourcePackage / sourceAppName string EVCC 当前选中的音乐来源,与 EVCC 音乐模块保持一致
positionMs long 音频播放位置,毫秒,非负
positionAtElapsedMs long 该位置对应的 Android SystemClock.elapsedRealtime(),非 Unix 时间
durationMs long 总时长,毫秒;0 表示未知
playbackSpeed number 播放倍速;客户端仅在 playing 且数值有效时外推
currentLyric string 当前歌词行;未知时为空
lineStartMs / lineEndMs long 当前行在原歌词时间轴上的开始与结束毫秒;未知为 -1,最后一行结束可能未知
lyricOffsetMs long 沿用 EVCC 音乐桥接偏移;以 positionMs + lyricOffsetMs 在原歌词时间轴中定位
lyricsAvailable boolean 是否有可发送的完整歌词正文;不表示当前句一定非空
lyricsFormat string lrc(带时间)、plain(纯文本整首)、none
lyricsRevision long 完整歌词内容或歌曲变化时递增;清空也使旧版本失效
bpm number 当前有效 BPM;优先当前高精分段,未覆盖时使用整曲值;未识别为 0
bpmStatus string unknown、matched、no_match;暂停保留已经识别的 BPM
textTruncated boolean 异常长的元数据或当前行是否被截短;不影响完整歌词正文

元数据的 UTF-16 长度上限:歌名、歌手、专辑、来源包名各 256,应用名 128,当前行 1024;截短不切断代理对。高频状态不包含封面、音频、整首歌词正文或完整 BPM 分段表。

enabled=false 或 available=false 时,歌曲、来源、当前行、完整歌词、BPM、进度全部清空,播放状态为 none。清空帧仍保留发布序号和歌词版本,不得继续显示缓存歌曲。普通暂停保留当前歌曲、歌词和 BPM,并冻结位置。

4. 完整歌词分片

{
  "schemaVersion": 1,
  "streamId": "e806e643-c608-47d2-b29d-f78e317c1104",
  "trackId": "opaque-track-identifier",
  "lyricsRevision": 3,
  "chunkIndex": 0,
  "chunkCount": 2,
  "format": "lrc",
  "text": "[00:00.00]第一句\n[00:12.00]当前这一句歌词\n"
}
  • chunkIndex 从 0 开始,chunkCount 是当前文档总片数。按序拼接 text,不要自行添加换行或再次 URL 解码。
  • 分片身份为 (streamId, trackId, lyricsRevision),必须匹配当前音乐状态。相同身份重复片可忽略,旧歌和旧版本片直接丢弃。
  • 每片 text 最多 4096 个 UTF-16 单元,不切断 Unicode 代理对;包含 JSON 转义和接口头的实际 Parcel 不超过 64 KiB。完整歌词不被静默截断。
  • EVCC 先向同一个回调发送关联音乐状态,再发送歌词片。歌词只在初次订阅、版本变化或重新注册时发送,不随每次进度重复。
  • 切歌、歌词版本改变、共享停用或不可用时,立即丢弃未完成的旧文档。整首歌词尚未收齐时,可显示 currentLyric。
  • 上游仅提供当前一句时,currentLyric 正常更新,lyricsAvailable=false,没有完整歌词片。当前句不能作为整首歌词保存。
  • 清空通过音乐状态通知,不发送空歌词文档。短暂丢片可重新调用 registerMusicCallback() 获取当前状态及整首歌词;客户端等待期间使用“最后收到歌词片后 3 秒”超时,不能从第一片起固定计时,以免超长文档被不断打断。

5. 推送频率与时间处理

首次注册、歌曲或来源变化、播放状态变化、可识别的进度跳转、当前行变化、歌词加载完成及 BPM 改变会立即安排推送;同一个调度轮次中的更新会合并。只有进度变化时最多每 500 ms 一帧。暂停期间不持续发送重复帧。

源没有显式跳转事件时,EVCC 将与原播放时钟偏差超过 250 ms 的位置更新视为跳转;更小的位置修正随下一进度帧同步。银河可以在两帧之间平滑外推:

仅当 enabled、available 为 true 且 playbackState 为 playing:
position = positionMs + max(0, elapsedRealtimeNow - positionAtElapsedMs) * playbackSpeed
否则:position = positionMs
position 至少为 0;durationMs > 0 时不得超过 durationMs。
歌词定位使用 position + lyricOffsetMs;音频进度条使用 position。

两端位于同一 Android 系统,可以共享 elapsedRealtime 时间基准。不要用 System.currentTimeMillis() 外推;EVCC/车机重启后以新绑定和新状态重新建立基线。

6. 连接、清空与恢复时序

银河绑定 EVCC
  → 查询 getProtocolVersion(),确认 >= 3
  → registerMusicCallback(callback)
  ← 当前音乐状态
  ← 当前完整歌词各片(如果有)
  → 可选 getMusicSnapshotJson(),按 sequence 去重
  ← 后续状态、当前行、进度及 BPM

关闭米德宝 AIDL / 云端撤销
  ← enabled=false 的清空状态
  → 银河清理歌曲、歌词缓存和进度动画
重新开启
  ← 已保留的合法订阅自动恢复当前状态及完整歌词

Binder 断开 / EVCC 重启
  → 银河立即清空展示,取消旧回调任务
  → 重新绑定,为新连接创建新回调对象
  → 重置 stream/sequence 基线并重新注册

注册后主动读取与初次回调可能重复或交错。只接收当前绑定代次的数据,同一 stream 丢弃 sequence <= 已应用序号 的状态。只有建立新绑定后才接受新的 stream;旧绑定已排队的回调不得覆盖新连接。

回调在 Binder 线程触发,JSON 解析、歌词拼接与 UI 更新应转移到客户端工作线程及主线程。EVCC 只保留最新音乐状态和当前歌词版本;回调异常或客户端死亡会清理订阅,恢复后需重连注册。回调入队不代表银河已执行任何业务动作。

7. 接入示例与验收

仓库提供可编译 Java 示例,目录为 docs/examples/midebao-music/:

  • GalaxyMusicClient.java:绑定、版本查询、独立回调、重连、歌词丢片重同步、主线程通知。
  • MusicReceiverState.java:状态去重、旧版本丢弃、歌词重组、进度外推,无 Android 依赖,可单独测试。
  • aidl/com/kooo/evcc/midebao/:与服务端一致的三个 AIDL 文件。
  • README.md:集成方法和验证说明。
  • consumer-rules.pro:发布包的 AIDL 描述符保留规则。

示例应集成进已获认可签名的银河应用,单独编译同名测试 APK 不会自动获得授权。示例的重组缓存限制为 8192 片,每片 4096 UTF-16 单元,超过时报告异常,不展示部分文档。

对接验收至少包括:不配置车辆属性即可接收音乐;仅开启旧版 AIDL 的其他应用无法读取新增音乐;暂停/恢复/切歌/跳转/倍速;完整、仅当前句及无歌词;歌词时间偏移;BPM 延迟、分段及无匹配;开关关闭清空及重新开启;EVCC 与银河各自重启;长歌词分片、重复片、旧版本和断线恢复。EVCC 编译或逻辑测试通过不能替代双方车机联调。