feat: 增加媒体格式校验,支持 JPEG、PNG、GIF、MP4、WebM、WebP 格式
This commit is contained in:
@@ -110,7 +110,7 @@ sdk.deleteFile(key: "file-key") { result in /* ... */ }
|
|||||||
### 文件传输
|
### 文件传输
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
// 一步完成:自动 ANI 转换 → prepare → transfer
|
// 一步完成:格式校验 → ANI 转换 → prepare → transfer
|
||||||
sdk.transferMedia(fileUrl: "https://example.com/video.mp4") { result in
|
sdk.transferMedia(fileUrl: "https://example.com/video.mp4") { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success: print("Transfer complete")
|
case .success: print("Transfer complete")
|
||||||
@@ -120,6 +120,13 @@ sdk.transferMedia(fileUrl: "https://example.com/video.mp4") { result in
|
|||||||
// 进度通过 delegate didUpdateProgress 回调
|
// 进度通过 delegate didUpdateProgress 回调
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**支持的媒体格式:** JPEG、PNG、GIF、MP4、WebM、WebP
|
||||||
|
|
||||||
|
格式校验规则:
|
||||||
|
- 优先从 URL path 提取扩展名(自动忽略查询参数)
|
||||||
|
- 无扩展名时通过 HEAD 请求检查 `Content-Type`
|
||||||
|
- 不支持的格式返回 `DuooomiBleError.unsupportedFormat`
|
||||||
|
|
||||||
### 固件升级
|
### 固件升级
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public enum DuooomiBleError: LocalizedError {
|
|||||||
case transferFailed(String)
|
case transferFailed(String)
|
||||||
case invalidFrame
|
case invalidFrame
|
||||||
case permissionDenied
|
case permissionDenied
|
||||||
|
case unsupportedFormat(String)
|
||||||
|
|
||||||
public var errorDescription: String? {
|
public var errorDescription: String? {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -64,6 +65,8 @@ public enum DuooomiBleError: LocalizedError {
|
|||||||
return "Invalid protocol frame"
|
return "Invalid protocol frame"
|
||||||
case .permissionDenied:
|
case .permissionDenied:
|
||||||
return "Bluetooth permission denied"
|
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
|
// 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) {
|
public func transferMedia(fileUrl: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
guard connectedDevice != nil else {
|
guard connectedDevice != nil else {
|
||||||
@@ -437,14 +488,21 @@ public final class DuooomiBleSDK: NSObject {
|
|||||||
BleLog.i("Direct ANI transfer: \(url)", "Transfer")
|
BleLog.i("Direct ANI transfer: \(url)", "Transfer")
|
||||||
transferMediaStep2(aniUrl: url, originalUrl: url, completion: completion)
|
transferMediaStep2(aniUrl: url, originalUrl: url, completion: completion)
|
||||||
} else {
|
} else {
|
||||||
BleLog.i("Converting to ANI: \(url)", "Transfer")
|
validateMediaFormat(url: url) { [weak self] result in
|
||||||
aniConverter.convert(fileUrl: url) { [weak self] result in
|
|
||||||
switch result {
|
switch result {
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(.failure(DuooomiBleError.transferFailed("ANI conversion failed: \(error.localizedDescription)")))
|
completion(.failure(error))
|
||||||
case .success(let aniUrl):
|
case .success:
|
||||||
BleLog.i("ANI ready: \(aniUrl)", "Transfer")
|
BleLog.i("Converting to ANI: \(url)", "Transfer")
|
||||||
self?.transferMediaStep2(aniUrl: aniUrl, originalUrl: url, completion: completion)
|
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