feat: 增加媒体格式校验,支持 JPEG、PNG、GIF、MP4、WebM、WebP 格式
This commit is contained in:
@@ -110,7 +110,7 @@ sdk.deleteFile(key: "file-key") { result in /* ... */ }
|
||||
### 文件传输
|
||||
|
||||
```swift
|
||||
// 一步完成:自动 ANI 转换 → prepare → transfer
|
||||
// 一步完成:格式校验 → ANI 转换 → prepare → transfer
|
||||
sdk.transferMedia(fileUrl: "https://example.com/video.mp4") { result in
|
||||
switch result {
|
||||
case .success: print("Transfer complete")
|
||||
@@ -120,6 +120,13 @@ sdk.transferMedia(fileUrl: "https://example.com/video.mp4") { result in
|
||||
// 进度通过 delegate didUpdateProgress 回调
|
||||
```
|
||||
|
||||
**支持的媒体格式:** JPEG、PNG、GIF、MP4、WebM、WebP
|
||||
|
||||
格式校验规则:
|
||||
- 优先从 URL path 提取扩展名(自动忽略查询参数)
|
||||
- 无扩展名时通过 HEAD 请求检查 `Content-Type`
|
||||
- 不支持的格式返回 `DuooomiBleError.unsupportedFormat`
|
||||
|
||||
### 固件升级
|
||||
|
||||
```swift
|
||||
|
||||
@@ -35,6 +35,7 @@ public enum DuooomiBleError: LocalizedError {
|
||||
case transferFailed(String)
|
||||
case invalidFrame
|
||||
case permissionDenied
|
||||
case unsupportedFormat(String)
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
@@ -64,6 +65,8 @@ public enum DuooomiBleError: LocalizedError {
|
||||
return "Invalid protocol frame"
|
||||
case .permissionDenied:
|
||||
return "Bluetooth permission denied"
|
||||
case .unsupportedFormat(let ext):
|
||||
return "Unsupported file format: \(ext). Supported: jpeg, png, gif, mp4, webm, webp"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,6 +419,57 @@ public final class DuooomiBleSDK: NSObject {
|
||||
|
||||
// MARK: - High-Level APIs
|
||||
|
||||
private static let supportedMediaExtensions: Set<String> = [
|
||||
"jpeg", "jpg", "png", "gif", "mp4", "webm", "webp"
|
||||
]
|
||||
|
||||
private static let supportedMimeTypes: Set<String> = [
|
||||
"image/jpeg", "image/png", "image/gif", "image/webp",
|
||||
"video/mp4", "video/webm"
|
||||
]
|
||||
|
||||
private func extractExtension(from urlString: String) -> String? {
|
||||
guard let comps = URLComponents(string: urlString) else { return nil }
|
||||
let path = comps.path
|
||||
let ext = (path.lowercased() as NSString).pathExtension
|
||||
guard !ext.isEmpty else { return nil }
|
||||
return ext
|
||||
}
|
||||
|
||||
private func validateMediaFormat(url: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if let ext = extractExtension(from: url) {
|
||||
if DuooomiBleSDK.supportedMediaExtensions.contains(ext) {
|
||||
completion(.success(()))
|
||||
} else {
|
||||
completion(.failure(DuooomiBleError.unsupportedFormat(ext)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let requestUrl = URL(string: url) else {
|
||||
completion(.failure(DuooomiBleError.transferFailed("Invalid URL: \(url)")))
|
||||
return
|
||||
}
|
||||
var request = URLRequest(url: requestUrl)
|
||||
request.httpMethod = "HEAD"
|
||||
URLSession.shared.dataTask(with: request) { _, response, error in
|
||||
if let error = error {
|
||||
completion(.failure(DuooomiBleError.transferFailed("Format check failed: \(error.localizedDescription)")))
|
||||
return
|
||||
}
|
||||
guard let mime = (response as? HTTPURLResponse)?.allHeaderFields["Content-Type"] as? String else {
|
||||
completion(.failure(DuooomiBleError.unsupportedFormat("unknown")))
|
||||
return
|
||||
}
|
||||
let baseMime = mime.components(separatedBy: ";").first?.trimmingCharacters(in: .whitespaces).lowercased() ?? ""
|
||||
if DuooomiBleSDK.supportedMimeTypes.contains(baseMime) {
|
||||
completion(.success(()))
|
||||
} else {
|
||||
completion(.failure(DuooomiBleError.unsupportedFormat(baseMime)))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
/// 传输媒体文件到设备(一步完成)
|
||||
public func transferMedia(fileUrl: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard connectedDevice != nil else {
|
||||
@@ -437,14 +488,21 @@ public final class DuooomiBleSDK: NSObject {
|
||||
BleLog.i("Direct ANI transfer: \(url)", "Transfer")
|
||||
transferMediaStep2(aniUrl: url, originalUrl: url, completion: completion)
|
||||
} else {
|
||||
BleLog.i("Converting to ANI: \(url)", "Transfer")
|
||||
aniConverter.convert(fileUrl: url) { [weak self] result in
|
||||
validateMediaFormat(url: url) { [weak self] result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
completion(.failure(DuooomiBleError.transferFailed("ANI conversion failed: \(error.localizedDescription)")))
|
||||
case .success(let aniUrl):
|
||||
BleLog.i("ANI ready: \(aniUrl)", "Transfer")
|
||||
self?.transferMediaStep2(aniUrl: aniUrl, originalUrl: url, completion: completion)
|
||||
completion(.failure(error))
|
||||
case .success:
|
||||
BleLog.i("Converting to ANI: \(url)", "Transfer")
|
||||
self?.aniConverter.convert(fileUrl: url) { [weak self] result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
completion(.failure(DuooomiBleError.transferFailed("ANI conversion failed: \(error.localizedDescription)")))
|
||||
case .success(let aniUrl):
|
||||
BleLog.i("ANI ready: \(aniUrl)", "Transfer")
|
||||
self?.transferMediaStep2(aniUrl: aniUrl, originalUrl: url, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user