demo app + SDK updates:\n- Add direct ANI transfer (prepare -> transfer)\n- Add firmware update flow (fetch latest w/ x-api-key, OTA transfer)\n- Extract x-api-key to NetworkConstants\n- Rename example app to demo; move folder and entry to DemoApp\n- Switch from SPM package dependency to local framework target\n- Enable automatic Info.plist generation for framework target\n- Add placeholder test to satisfy SwiftPM test target\n- Regenerate XcodeGen project (demo.xcodeproj)

This commit is contained in:
km2023
2026-04-09 18:41:56 +08:00
parent c33e7d3fa1
commit 7d9ff3081d
30 changed files with 2594 additions and 516 deletions

View File

@@ -0,0 +1,84 @@
import Foundation
/// ANI
struct AniConverterError: LocalizedError {
let message: String
var errorDescription: String? { message }
}
/// ANI
///
/// duooomi-ble-sdk/CLAUDE.md " > ANI API"
/// `videoUrl` `width / height / fps`
enum AniConverter {
private static let endpoint = URL(string: "https://api.mixvideo.bowong.cc/api/auth/loomart/file/convert-to-ani")!
/// session ANI -1005
private static let session: URLSession = {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 120
config.timeoutIntervalForResource = 180
config.waitsForConnectivity = true
return URLSession(configuration: config)
}()
/// ANI ani CDN URL
/// - Parameter videoUrl: URL
/// - Returns: ani CDN URL
/// - Throws:
/// - Note: -1005
static func convert(videoUrl: String) async throws -> String {
do {
return try await performConvert(videoUrl: videoUrl)
} catch let error as NSError where error.code == NSURLErrorNetworkConnectionLost {
// -1005
return try await performConvert(videoUrl: videoUrl)
}
}
///
/// - Parameter videoUrl: URL
/// - Returns: ani URL
/// - Throws:
private static func performConvert(videoUrl: String) async throws -> String {
var request = URLRequest(url: endpoint)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "content-type")
request.setValue(NetworkConfig.apiKey, forHTTPHeaderField: "x-api-key")
request.timeoutInterval = 120
let body: [String: Any] = [
"videoUrl": videoUrl,
"width": 360,
"height": 360,
"fps": "24",
]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await session.data(for: request)
if let http = response as? HTTPURLResponse, !(200..<300).contains(http.statusCode) {
throw AniConverterError(message: "HTTP \(http.statusCode)")
}
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw AniConverterError(message: "Invalid JSON response")
}
guard (json["success"] as? Bool) == true,
let outer = json["data"] as? [String: Any] else {
throw AniConverterError(message: "ANI API reported failure")
}
if (outer["status"] as? Bool) != true {
let msg = (outer["msg"] as? String) ?? "ANI conversion failed"
throw AniConverterError(message: msg)
}
guard let aniUrl = outer["data"] as? String, !aniUrl.isEmpty else {
throw AniConverterError(message: "Missing ani url in response")
}
return aniUrl
}
}