feat: 增加媒体格式校验,支持 JPEG、PNG、GIF、MP4、WebM、WebP 格式
This commit is contained in:
10
README.md
10
README.md
@@ -169,14 +169,20 @@ sdk.deleteFile(fileKey)
|
|||||||
### 文件传输
|
### 文件传输
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
// 传输媒体文件 (自动转换为 ANI 格式)
|
// 传输媒体文件 (格式校验 → ANI 转换 → prepare → transfer)
|
||||||
// 支持: mp4, jpg, png, ani
|
|
||||||
sdk.transferMedia(fileUrl)
|
sdk.transferMedia(fileUrl)
|
||||||
|
|
||||||
// 监听进度
|
// 监听进度
|
||||||
sdk.transferProgress.collect { percent -> /* 0-100 */ }
|
sdk.transferProgress.collect { percent -> /* 0-100 */ }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**支持的媒体格式:** JPEG、PNG、GIF、MP4、WebM、WebP
|
||||||
|
|
||||||
|
格式校验规则:
|
||||||
|
- 优先从 URL path 提取扩展名(自动忽略查询参数)
|
||||||
|
- 无扩展名时通过 HEAD 请求检查 `Content-Type`
|
||||||
|
- 不支持的格式抛出 `DuooomiBleError.UnsupportedFormat`
|
||||||
|
|
||||||
### 固件升级
|
### 固件升级
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URI
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
|
class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
|
||||||
@@ -303,6 +304,46 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
|
|||||||
|
|
||||||
// MARK: - High-Level APIs
|
// MARK: - High-Level APIs
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val SUPPORTED_EXTENSIONS = setOf("jpeg", "jpg", "png", "gif", "mp4", "webm", "webp")
|
||||||
|
private val SUPPORTED_MIME_TYPES = setOf(
|
||||||
|
"image/jpeg", "image/png", "image/gif", "image/webp",
|
||||||
|
"video/mp4", "video/webm"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractExtension(urlString: String): String? {
|
||||||
|
return try {
|
||||||
|
val path = URI(urlString).path ?: return null
|
||||||
|
val dot = path.lastIndexOf('.')
|
||||||
|
if (dot >= 0 && dot < path.length - 1) path.substring(dot + 1).lowercase() else null
|
||||||
|
} catch (_: Exception) { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun validateMediaFormat(url: String) {
|
||||||
|
val ext = extractExtension(url)
|
||||||
|
if (ext != null) {
|
||||||
|
if (SUPPORTED_EXTENSIONS.contains(ext)) return
|
||||||
|
throw DuooomiBleError.UnsupportedFormat(ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
val mime = withContext(Dispatchers.IO) {
|
||||||
|
val conn = java.net.URL(url).openConnection() as HttpURLConnection
|
||||||
|
try {
|
||||||
|
conn.requestMethod = "HEAD"
|
||||||
|
conn.connectTimeout = 10_000
|
||||||
|
conn.readTimeout = 10_000
|
||||||
|
conn.contentType?.split(";")?.firstOrNull()?.trim()?.lowercase()
|
||||||
|
} finally {
|
||||||
|
conn.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mime == null || !SUPPORTED_MIME_TYPES.contains(mime)) {
|
||||||
|
throw DuooomiBleError.UnsupportedFormat(mime ?: "unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun transferMedia(fileUrl: String) {
|
suspend fun transferMedia(fileUrl: String) {
|
||||||
ensureConnected()
|
ensureConnected()
|
||||||
val url = fileUrl.trim()
|
val url = fileUrl.trim()
|
||||||
@@ -315,6 +356,7 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
|
|||||||
aniUrl = url
|
aniUrl = url
|
||||||
BleLog.i("Direct ANI transfer: $url", "Transfer")
|
BleLog.i("Direct ANI transfer: $url", "Transfer")
|
||||||
} else {
|
} else {
|
||||||
|
validateMediaFormat(url)
|
||||||
BleLog.i("Converting to ANI: $url", "Transfer")
|
BleLog.i("Converting to ANI: $url", "Transfer")
|
||||||
try {
|
try {
|
||||||
aniUrl = aniConverter.convert(url)
|
aniUrl = aniConverter.convert(url)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ sealed class DuooomiBleError(message: String) : Exception(message) {
|
|||||||
class DeleteFileFailed(val status: Int) : DuooomiBleError("Delete failed: status=$status")
|
class DeleteFileFailed(val status: Int) : DuooomiBleError("Delete failed: status=$status")
|
||||||
class PrepareTransferFailed(val status: String) : DuooomiBleError("Prepare failed: $status")
|
class PrepareTransferFailed(val status: String) : DuooomiBleError("Prepare failed: $status")
|
||||||
class TransferFailed(reason: String) : DuooomiBleError("Transfer failed: $reason")
|
class TransferFailed(reason: String) : DuooomiBleError("Transfer failed: $reason")
|
||||||
|
class UnsupportedFormat(val format: String) : DuooomiBleError("Unsupported file format: $format. Supported: jpeg, png, gif, mp4, webm, webp")
|
||||||
object InvalidFrame : DuooomiBleError("Invalid protocol frame")
|
object InvalidFrame : DuooomiBleError("Invalid protocol frame")
|
||||||
object PermissionDenied : DuooomiBleError("Bluetooth permission denied")
|
object PermissionDenied : DuooomiBleError("Bluetooth permission denied")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user