feat(sdk): 同步 iOS — 帧间隔 frameIntervalMs 配置化(默认 35ms)
1:1 复刻 iOS:DuooomiBleConfig 加 frameIntervalMs,BleProtocolService 构造期注入,sendFrames 改用 coerceIn(0,500)。清掉 FRAME_INTERVAL_MS 常量。 demo 加 ConfigField + SharedPreferences 持久化。 越界 warn 字符串与 iOS 逐字一致。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,7 @@ fun DemoScreen(
|
|||||||
var configBrand by remember(sdk) { mutableStateOf(config.brand) }
|
var configBrand by remember(sdk) { mutableStateOf(config.brand) }
|
||||||
var configApiKey by remember(sdk) { mutableStateOf(config.apiKey) }
|
var configApiKey by remember(sdk) { mutableStateOf(config.apiKey) }
|
||||||
var configScanPrefix by remember(sdk) { mutableStateOf(config.scanPrefix) }
|
var configScanPrefix by remember(sdk) { mutableStateOf(config.scanPrefix) }
|
||||||
|
var configFrameIntervalMs by remember(sdk) { mutableStateOf(config.frameIntervalMs) }
|
||||||
|
|
||||||
// Local state
|
// Local state
|
||||||
var userId by remember { mutableStateOf("Cjgex9kTTTJq9u9RMOnc5hiIZTvrOOq3") }
|
var userId by remember { mutableStateOf("Cjgex9kTTTJq9u9RMOnc5hiIZTvrOOq3") }
|
||||||
@@ -137,11 +138,12 @@ fun DemoScreen(
|
|||||||
ConfigField("brand", configBrand) { configBrand = it }
|
ConfigField("brand", configBrand) { configBrand = it }
|
||||||
ConfigField("apiKey", configApiKey) { configApiKey = it }
|
ConfigField("apiKey", configApiKey) { configApiKey = it }
|
||||||
ConfigField("scanPrefix", configScanPrefix) { configScanPrefix = it }
|
ConfigField("scanPrefix", configScanPrefix) { configScanPrefix = it }
|
||||||
|
ConfigField("frameIntervalMs", configFrameIntervalMs) { configFrameIntervalMs = it }
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
onReload(DemoConfig(configBrand, configApiKey, configScanPrefix))
|
onReload(DemoConfig(configBrand, configApiKey, configScanPrefix, configFrameIntervalMs))
|
||||||
log(
|
log(
|
||||||
"SDK reloaded with brand=$configBrand, prefix=$configScanPrefix",
|
"SDK reloaded with brand=$configBrand, prefix=$configScanPrefix, frameMs=$configFrameIntervalMs",
|
||||||
LogLevel.SUCCESS
|
LogLevel.SUCCESS
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
brand = prefs.getString(KEY_BRAND, DemoSecrets.BRAND) ?: DemoSecrets.BRAND,
|
brand = prefs.getString(KEY_BRAND, DemoSecrets.BRAND) ?: DemoSecrets.BRAND,
|
||||||
apiKey = prefs.getString(KEY_API_KEY, DemoSecrets.API_KEY) ?: DemoSecrets.API_KEY,
|
apiKey = prefs.getString(KEY_API_KEY, DemoSecrets.API_KEY) ?: DemoSecrets.API_KEY,
|
||||||
scanPrefix = prefs.getString(KEY_SCAN_PREFIX, DemoSecrets.SCAN_NAME_PREFIX)
|
scanPrefix = prefs.getString(KEY_SCAN_PREFIX, DemoSecrets.SCAN_NAME_PREFIX)
|
||||||
?: DemoSecrets.SCAN_NAME_PREFIX
|
?: DemoSecrets.SCAN_NAME_PREFIX,
|
||||||
|
frameIntervalMs = prefs.getString(KEY_FRAME_INTERVAL_MS, "35") ?: "35"
|
||||||
)
|
)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
@@ -70,7 +71,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
config = DuooomiBleConfig(
|
config = DuooomiBleConfig(
|
||||||
apiKey = c.apiKey,
|
apiKey = c.apiKey,
|
||||||
brand = c.brand,
|
brand = c.brand,
|
||||||
scanNamePrefix = c.scanPrefix
|
scanNamePrefix = c.scanPrefix,
|
||||||
|
frameIntervalMs = c.frameIntervalMs.toIntOrNull() ?: 35
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -79,6 +81,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
.putString(KEY_BRAND, c.brand)
|
.putString(KEY_BRAND, c.brand)
|
||||||
.putString(KEY_API_KEY, c.apiKey)
|
.putString(KEY_API_KEY, c.apiKey)
|
||||||
.putString(KEY_SCAN_PREFIX, c.scanPrefix)
|
.putString(KEY_SCAN_PREFIX, c.scanPrefix)
|
||||||
|
.putString(KEY_FRAME_INTERVAL_MS, c.frameIntervalMs)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,11 +107,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
const val KEY_BRAND = "demo.config.brand"
|
const val KEY_BRAND = "demo.config.brand"
|
||||||
const val KEY_API_KEY = "demo.config.apiKey"
|
const val KEY_API_KEY = "demo.config.apiKey"
|
||||||
const val KEY_SCAN_PREFIX = "demo.config.scanPrefix"
|
const val KEY_SCAN_PREFIX = "demo.config.scanPrefix"
|
||||||
|
const val KEY_FRAME_INTERVAL_MS = "demo.config.frameIntervalMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DemoConfig(
|
data class DemoConfig(
|
||||||
val brand: String,
|
val brand: String,
|
||||||
val apiKey: String,
|
val apiKey: String,
|
||||||
val scanPrefix: String
|
val scanPrefix: String,
|
||||||
|
val frameIntervalMs: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.duooomi.ble
|
package com.duooomi.ble
|
||||||
|
|
||||||
|
import com.duooomi.ble.utils.BleLog
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SDK 全局配置,在初始化时一次性传入。
|
* SDK 全局配置,在初始化时一次性传入。
|
||||||
*/
|
*/
|
||||||
@@ -19,8 +21,19 @@ data class DuooomiBleConfig(
|
|||||||
/** ANI 转换高度(像素) */
|
/** ANI 转换高度(像素) */
|
||||||
val aniHeight: Int = 360,
|
val aniHeight: Int = 360,
|
||||||
/** ANI 转换帧率 */
|
/** ANI 转换帧率 */
|
||||||
val aniFps: String = "24"
|
val aniFps: String = "24",
|
||||||
|
/**
|
||||||
|
* 协议帧之间的发送间隔(毫秒)。合法区间 `[0, 500]`,越界仅 warn,使用点会 clamp。
|
||||||
|
* 集成方一般无需调整;调高用于排查接收端丢帧/拥塞。默认 35。
|
||||||
|
*/
|
||||||
|
val frameIntervalMs: Int = 35
|
||||||
) {
|
) {
|
||||||
|
init {
|
||||||
|
if (frameIntervalMs !in 0..500) {
|
||||||
|
BleLog.w("frameIntervalMs=$frameIntervalMs out of [0,500], will clamp at use site", "Config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val normalizedCdnHost: String
|
val normalizedCdnHost: String
|
||||||
get() = if (cdnHost.endsWith("/")) cdnHost else "$cdnHost/"
|
get() = if (cdnHost.endsWith("/")) cdnHost else "$cdnHost/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
|
|||||||
// MARK: - Internal Services
|
// MARK: - Internal Services
|
||||||
|
|
||||||
private val bleClient = BleClient(context)
|
private val bleClient = BleClient(context)
|
||||||
private val protocolService = BleProtocolService(bleClient)
|
private val protocolService = BleProtocolService(bleClient, config.frameIntervalMs)
|
||||||
private val timeProvider = BeijingTimeProvider()
|
private val timeProvider = BeijingTimeProvider()
|
||||||
private val deviceInfoService = DeviceInfoService(protocolService, timeProvider)
|
private val deviceInfoService = DeviceInfoService(protocolService, timeProvider)
|
||||||
private val fileTransferService = FileTransferService(protocolService)
|
private val fileTransferService = FileTransferService(protocolService)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ object FrameConstants {
|
|||||||
const val MAX_DATA_SIZE = 496
|
const val MAX_DATA_SIZE = 496
|
||||||
const val HEADER_SIZE = 8
|
const val HEADER_SIZE = 8
|
||||||
const val FOOTER_SIZE = 1
|
const val FOOTER_SIZE = 1
|
||||||
const val FRAME_INTERVAL_MS = 35L
|
|
||||||
const val SCREEN_SIZE = 360
|
const val SCREEN_SIZE = 360
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
internal class BleProtocolService(private val client: BleClient) {
|
internal class BleProtocolService(
|
||||||
|
private val client: BleClient,
|
||||||
|
private val frameIntervalMs: Int
|
||||||
|
) {
|
||||||
|
|
||||||
private data class FragmentSession(
|
private data class FragmentSession(
|
||||||
val total: Int,
|
val total: Int,
|
||||||
@@ -69,9 +72,10 @@ internal class BleProtocolService(private val client: BleClient) {
|
|||||||
|
|
||||||
val total = frames.size
|
val total = frames.size
|
||||||
BleLog.d("Sending frames: total=$total, maxChunk=$safeMaxDataSize", "Protocol")
|
BleLog.d("Sending frames: total=$total, maxChunk=$safeMaxDataSize", "Protocol")
|
||||||
|
val effectiveInterval = frameIntervalMs.coerceIn(0, 500).toLong()
|
||||||
for ((index, frame) in frames.withIndex()) {
|
for ((index, frame) in frames.withIndex()) {
|
||||||
client.writeWithoutResponse(frame)
|
client.writeWithoutResponse(frame)
|
||||||
delay(FrameConstants.FRAME_INTERVAL_MS)
|
delay(effectiveInterval)
|
||||||
onProgress?.invoke((index + 1).toDouble() / total.toDouble())
|
onProgress?.invoke((index + 1).toDouble() / total.toDouble())
|
||||||
}
|
}
|
||||||
BleLog.i("Frames sent: total=$total", "Protocol")
|
BleLog.i("Frames sent: total=$total", "Protocol")
|
||||||
|
|||||||
Reference in New Issue
Block a user