native-sound-integration.md 6.3 KB

原生通知语音接入(2026-10-26)

本次追加覆盖 FCM/APNs 用户通知的前台、后台和锁屏语音。uniPush、消息中心 body、原业务 payload、商家/骑手默认声音保持原规则。不使用 VoIP,不新增通知或声音下载接口。服务端默认推送通道仍为 uni,测试入口直接使用原生发送。

资源交付

音频位于 assets/native-wav,完整文件和渠道映射见 manifest.json。63 个 WAV:9 场景 × 7 语种,16 kHz、单声道、16-bit Linear PCM,均不足 30 秒。由用户提供的 MP3 转换,原目录未修改;zh-CN/zh-TW 的语音分别统一选用提供的 zh-Hans/zh-Hant 源文件,不合并或覆盖原文件。

场景字段 值 文件名前缀
eventScene RIDER_ACCEPTED rider_accepted
eventScene ORDER_DELIVERING order_delivering
eventScene ORDER_DELIVERED order_delivered
eventScene MERCHANT_ACCEPTED merchant_accepted
eventScene MERCHANT_READY merchant_ready
eventScene PAYMENT_SUCCEEDED payment_succeeded
eventScene ORDER_CANCELLED order_cancelled
eventType FLASH_PICKUP_STOP_COMPLETED flash_pickup_stop_completed
eventType FLASH_DELIVERY_STOP_COMPLETED flash_delivery_stop_completed

文件格式:{场景小写}_{语言后缀}.wav,例如 rider_accepted_zh_hant.wav。

传入/已保存语言示例 资源后缀
zhHant、zh-Hant、zh-TW、zh_TW、tw、zh-Hant-TW zh_hant
zhHans、zh-Hans、zh-CN、zh_CN、zh-Hans-CN zh_hans
en、en-US en
th、th-TH th
id、id-ID、in id
ms、ms-MY ms
tl、fil、fil-PH tl
未设置、空白、未知(含 vi、未指定繁简的 zh) zh_hant

业务通知读取现有 GET /changeLanguages?lang=zhHant&id=用户ID 保存的语言,使用与该接口相同的 Jedis DB 0 和原始用户 ID 键;App 在登录/切换语言时需调用既有接口。此处不改造该旧接口权限或签名。缓存不可用时通知仍发送,声音使用繁中。测试入口直接读请求 language,不查用户或语言缓存。缺场景/未知场景不猜语音,保留原默认通知声音。

iOS

将 WAV 作为资源加入用户 App target,确认文件真正包含在安装包的 bundle 中;也可使用系统支持的 Library/Sounds 目录。后端 aps.sound 直接指定文件名,同时在 userInfo 顶层放 sound,供前台处理。

{
  "aps": {
    "alert": {"title": "订单通知", "body": "骑手已接单"},
    "sound": "rider_accepted_zh_hant.wav"
  },
  "sound": "rider_accepted_zh_hant.wav",
  "payload": "{\"eventScene\":\"RIDER_ACCEPTED\"}"
}

WAV 是 Apple 支持的系统通知声音格式,不需要再转 CAF;不能只改 MP3 扩展名。申请通知及声音权限。前台可在系统回调允许 sound,或按顶层 sound 手动播放,二选一以免双响。后台/锁屏由系统播放,点击通知、加载历史消息、恢复前台不补播。静音、专注模式及用户权限可能抑制声音,后端不能强制绕过。

APNs 网关仍按现有 apns-environment 选择,与本功能无关;签名环境必须匹配 Token。测试接口成功仅表示推送服务接受,不表示手机已播放。

Android

将 WAV 复制到用户 App 原生工程 res/raw/,文件名保持小写字母与下划线。首次启动时按 manifest 创建全部 63 个声音渠道,确保后台通知到达前渠道已存在。渠道 ID 为 user_voice_{不含扩展名的资源名}_v1,例如 user_voice_rider_accepted_zh_hant_v1。

创建渠道时:IMPORTANCE_HIGH,sound URI 指向该 raw 资源,AudioAttributes 使用 USAGE_NOTIFICATION;渠道名称需由 App 国际化。后端发送 notification + data:

{
  "android": {
    "priority": "high",
    "notification": {
      "sound": "rider_accepted_zh_hant",
      "default_sound": false,
      "channel_id": "user_voice_rider_accepted_zh_hant_v1"
    }
  },
  "data": {
    "sound": "rider_accepted_zh_hant.wav",
    "channelId": "user_voice_rider_accepted_zh_hant_v1",
    "payload": "{\"eventScene\":\"RIDER_ACCEPTED\"}"
  }
}

Android 8+ 以渠道声音为准,后端传 sound 无法覆盖已有渠道设置;渠道缺失可能回退默认渠道。渠道一旦创建便不能靠重新 setSound 修改,未来更换绑定声音需双方约定新版本渠道 ID。Android 13+ 申请通知权限。后台/锁屏使用系统通知;前台由 App 根据 data.sound 播放,或使用 data.channelId 发本地通知,二选一。用户关闭渠道声音时不能保证响铃。

Apipost 测试

匿名 POST /system/push/testNative,Content-Type: application/json,不切换业务推送模式,不需要用户 ID。沿用已有 token 手填方式,新增可选 language;场景仍在 payload 中,payload 是字符串。

{
  "appType": "user",
  "platform": "android",
  "deviceToken": "填写手机当前原生Token",
  "title": "语音测试",
  "content": "骑手已接单",
  "language": "zhHant",
  "payload": "{\"eventScene\":\"RIDER_ACCEPTED\"}"
}

iOS 将 platform 改为 ios 并填写对应 APNs Token。闪送站点示例:payload 使用 {"eventType":"FLASH_PICKUP_STOP_COMPLETED"} 的 JSON 字符串。省略 language 或传未知值,应使用繁中声音。老请求不含场景时仍按原默认声音发送。

验收边界

  • 后端定向测试验证场景/语言映射、同一语言缓存源、缓存失败回退、FCM/APNs 载荷、匿名测试入口及 uni 行为隔离;音频逐个验证编码和时长。
  • App 需验证资源打包、渠道初始化及权限后,分别测试 iOS/Android 前台、后台和锁屏,检查正确语种、单次播放、点击不补播。
  • 本仓库不修改 App,不部署服务,不代表已完成真机通知投递、音频内容人工听审或系统播放验证。新用户 App 未带资源/渠道时,服务端参数本身不能使其播放这些语音。

平台依据:Apple 自定义通知声音、FCM 消息字段、Android 通知渠道。