feat: PlayMode 切换 + bind/unbind/setPlayMode 携带北京时间

主要改动(与 iOS SDK 1:1 对齐):
- 新增 BeijingTimeProvider:苏宁公开 API → header Date → 本地+8h 三级 fallback;
  5 秒缓存 + Mutex 单飞复用
- 新增 PlayMode enum (SINGLE=0, LOOP=1)
- DeviceInfo 增加可选 loop 字段(旧固件无此字段时为 null)
- DeviceInfoService.bind/unbind/setPlayMode 共用 payload,带 time 字段;
  loop == null 时严格省略 key(与 expo zod.optional 对齐)
- DuooomiBleSDK.setPlayMode(mode, userId):复用 BIND_DEVICE 命令通道,
  isActivated 守卫;成功后自动刷新 deviceInfo
- Demo 增加 Single / Loop 按钮(gated by isActivated)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
km2023
2026-05-08 15:30:01 +08:00
parent 8b18292100
commit b4398518a9
6 changed files with 263 additions and 10 deletions

View File

@@ -5,6 +5,7 @@ import com.duooomi.ble.core.*
import com.duooomi.ble.models.*
import com.duooomi.ble.protocol.CommandType
import com.duooomi.ble.services.*
import com.duooomi.ble.utils.BeijingTimeProvider
import com.duooomi.ble.utils.BleLog
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@@ -45,7 +46,8 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
private val bleClient = BleClient(context)
private val protocolService = BleProtocolService(bleClient)
private val deviceInfoService = DeviceInfoService(protocolService)
private val timeProvider = BeijingTimeProvider()
private val deviceInfoService = DeviceInfoService(protocolService, timeProvider)
private val fileTransferService = FileTransferService(protocolService)
private val aniConverter = AniConverter(config)
private val firmwareService = FirmwareService(config)
@@ -229,6 +231,36 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
return resp
}
/**
* 切换播放模式0=单播 / 1=循环播放)。
*
* 复用 [CommandType.BIND_DEVICE] (0x0F) 命令通道;响应类型为 [BindingResponse]。
* 成功后自动刷新一次 `deviceInfo`(含最新 `loop` 字段),与 expo 端行为对齐。
*
* 前置:必须已 [bind] 完成(`isActivated == true`)。
* `bind` / `unbind` / `setPlayMode` 共享 0x0F / 0x12 opId 通道,**不能并发**。
*/
suspend fun setPlayMode(mode: com.duooomi.ble.models.PlayMode, userId: String): BindingResponse {
ensureConnected()
if (!_isActivated.value) {
BleLog.w("setPlayMode called before bind (isActivated=false)", "Command")
throw DuooomiBleError.BindingFailed("Device not bound; call bind(userId) first")
}
BleLog.d("Sending setPlayMode (mode=$mode, userId=$userId)", "Command")
val data = sendAndWait(CommandType.BIND_DEVICE) {
deviceInfoService.setPlayMode(userId, mode)
}
val resp = BindingResponse.fromJson(parseJsonResponse(data))
if (resp.success != 1) {
BleLog.w("setPlayMode failed: device rejected", "Command")
throw DuooomiBleError.BindingFailed("Set play mode failed")
}
BleLog.i("setPlayMode success: mode=$mode", "Command")
// 自动刷新 deviceInfo含最新 loop 字段)
runCatching { getDeviceInfo() }
return resp
}
suspend fun unbind(userId: String): UnbindResponse {
ensureConnected()
BleLog.d("Sending unbind (userId=$userId)", "Command")