feat(ios): add online device list and pairing code redemption
- Add API methods for fetching online devices and redeeming pairing codes - Add OnlineDevice model and corresponding ViewModel state management - Add UI sections for online device list and pairing code input in SetupPanel - Ensure access token is verified before connecting to signaling server
This commit is contained in:
@@ -41,6 +41,14 @@ struct ResolutionOption: Identifiable, Equatable {
|
||||
]
|
||||
}
|
||||
|
||||
/// 在线被控端(参考 Flutter BindingDevice.fromOnlineJson)
|
||||
struct OnlineDevice: Identifiable, Equatable {
|
||||
let deviceId: String
|
||||
let nickname: String
|
||||
|
||||
var id: String { deviceId }
|
||||
}
|
||||
|
||||
/// 主控端 ViewModel:串联信令、WebRTC、自编码解码与 UI 状态。
|
||||
/// 所有回调均已在主线程触发,@Published 属性直接驱动 SwiftUI。
|
||||
final class ControllerViewModel: NSObject, ObservableObject {
|
||||
@@ -67,6 +75,17 @@ final class ControllerViewModel: NSObject, ObservableObject {
|
||||
@Published var showAuthSheet: Bool = false
|
||||
@Published var alertMessage: String?
|
||||
|
||||
// MARK: - 设备发现与配对
|
||||
|
||||
/// 当前在线的被控端列表(GET /api/client/devices/online)。
|
||||
@Published var onlineDevices: [OnlineDevice] = []
|
||||
/// 是否正在加载在线设备列表。
|
||||
@Published var isLoadingOnlineDevices: Bool = false
|
||||
/// 配对码输入框内容。
|
||||
@Published var pairingCode: String = ""
|
||||
/// 是否正在兑换配对码。
|
||||
@Published var isRedeemingPairing: Bool = false
|
||||
|
||||
/// 是否正在录制远端视频
|
||||
@Published var isRecording: Bool = false
|
||||
/// 最近一次录制保存的文件路径(沙盒内)
|
||||
@@ -156,10 +175,12 @@ final class ControllerViewModel: NSObject, ObservableObject {
|
||||
alertMessage = "请先在右上角登录账号"
|
||||
return
|
||||
}
|
||||
loadOnlineDevices()
|
||||
showAuthSheet = true
|
||||
}
|
||||
|
||||
/// 鉴权弹窗确认后开始连接(已确保 accessToken 有效)。
|
||||
/// 鉴权弹窗确认后开始连接。先确保 accessToken 有效(参考 Flutter 的 connectSignaling 先 verify/refresh),
|
||||
/// 避免首连时直接带着过期令牌被服务端以 4001 关闭。
|
||||
func connect(authType: AuthType, authValue: String) {
|
||||
showAuthSheet = false
|
||||
pendingAuthType = authType
|
||||
@@ -167,12 +188,25 @@ final class ControllerViewModel: NSObject, ObservableObject {
|
||||
isConnecting = true
|
||||
statusText = "正在连接信令服务器..."
|
||||
|
||||
let signaling = SignalingClient(
|
||||
serverUrl: serverUrl.trimmingCharacters(in: .whitespaces),
|
||||
token: accessToken)
|
||||
signaling.delegate = self
|
||||
signalingClient = signaling
|
||||
signaling.connect()
|
||||
Task {
|
||||
do {
|
||||
let token = try await ensureAccessToken()
|
||||
accessToken = token
|
||||
await MainActor.run { [weak self] in
|
||||
guard let self else { return }
|
||||
let signaling = SignalingClient(
|
||||
serverUrl: self.serverUrl.trimmingCharacters(in: .whitespaces),
|
||||
token: token)
|
||||
signaling.delegate = self
|
||||
self.signalingClient = signaling
|
||||
signaling.connect()
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run { [weak self] in
|
||||
self?.failConnection((error as? ApiError)?.message ?? "令牌失效,请重新登录")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
@@ -433,6 +467,67 @@ final class ControllerViewModel: NSObject, ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// 拉取当前在线的被控端列表(GET /api/client/devices/online,仅在线设备)。
|
||||
/// 参考 Flutter RemoteController.loadOnlineDevices / BindingDevice.fromOnlineJson。
|
||||
func loadOnlineDevices() {
|
||||
guard let at = accessToken else { return }
|
||||
isLoadingOnlineDevices = true
|
||||
Task {
|
||||
do {
|
||||
let data = try await ApiClient.onlineDevices(accessToken: at)
|
||||
let list = (data["devices"] as? [[String: Any]]) ?? []
|
||||
let devices: [OnlineDevice] = list.compactMap { entry in
|
||||
guard let id = entry["deviceId"] as? String, !id.isEmpty else { return nil }
|
||||
let nickname = (entry["nickname"] as? String) ?? id
|
||||
return OnlineDevice(deviceId: id, nickname: nickname)
|
||||
}
|
||||
await MainActor.run { [weak self] in
|
||||
guard let self else { return }
|
||||
self.onlineDevices = devices
|
||||
self.isLoadingOnlineDevices = false
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run { [weak self] in
|
||||
self?.isLoadingOnlineDevices = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 配对码兑换:用被控端显示的配对码建立绑定(POST /api/client/pairing/redeem)。
|
||||
/// 参考 Flutter RemoteController.redeemPairingCode。
|
||||
func redeemPairingCode() {
|
||||
let code = pairingCode.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !code.isEmpty else {
|
||||
alertMessage = "请输入配对码"
|
||||
return
|
||||
}
|
||||
guard let at = accessToken else {
|
||||
alertMessage = "请先登录账号"
|
||||
return
|
||||
}
|
||||
isRedeemingPairing = true
|
||||
Task {
|
||||
do {
|
||||
let data = try await ApiClient.redeemPairingCode(accessToken: at, code: code)
|
||||
let deviceId = (data["deviceId"] as? String) ?? ""
|
||||
let nickname = (data["nickname"] as? String) ?? deviceId
|
||||
await MainActor.run { [weak self] in
|
||||
guard let self else { return }
|
||||
self.isRedeemingPairing = false
|
||||
self.pairingCode = ""
|
||||
self.alertMessage = "绑定成功:\(nickname)(\(deviceId))"
|
||||
self.loadOnlineDevices()
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run { [weak self] in
|
||||
self?.isRedeemingPairing = false
|
||||
self?.alertMessage = "配对失败:\((error as? ApiError)?.message ?? error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 拉取 TURN 短期凭证,覆盖默认 ICE(由 WebRTCClient 读取)。
|
||||
private func loadTurnCredentials() async {
|
||||
guard let at = accessToken else { return }
|
||||
|
||||
Reference in New Issue
Block a user