라이브룸 V2V 번역 자막 기능을 추가한다
라이브룸에서 진행자 언어와 기기 언어가 다를 때 자막 토글을 제공한다. 룸 정보 응답에 V2V 워커 토큰과 진행자 언어 코드를 포함한다. Agora V2V 에이전트 참여와 종료 API 연동을 추가한다
This commit is contained in:
128
SodaLive/Sources/Live/Room/V2V/V2vRepository.swift
Normal file
128
SodaLive/Sources/Live/Room/V2V/V2vRepository.swift
Normal file
@@ -0,0 +1,128 @@
|
||||
//
|
||||
// V2vRepository.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2/9/26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import Moya
|
||||
import CombineMoya
|
||||
|
||||
protocol V2VRepository {
|
||||
func join(request: V2VJoinRequest) -> AnyPublisher<String, V2VRepositoryError>
|
||||
func leave(agentId: String) -> AnyPublisher<Void, V2VRepositoryError>
|
||||
}
|
||||
|
||||
enum V2VRepositoryError: Error {
|
||||
case network(message: String)
|
||||
case decoding
|
||||
case business(message: String)
|
||||
|
||||
var userMessage: String {
|
||||
switch self {
|
||||
case .network(let message):
|
||||
return message
|
||||
case .decoding:
|
||||
return I18n.Common.commonError
|
||||
case .business(let message):
|
||||
return message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class V2VRepositoryImpl: V2VRepository {
|
||||
private let api: MoyaProvider<V2vApi>
|
||||
|
||||
init(api: MoyaProvider<V2vApi> = MoyaProvider<V2vApi>()) {
|
||||
self.api = api
|
||||
}
|
||||
|
||||
func join(request: V2VJoinRequest) -> AnyPublisher<String, V2VRepositoryError> {
|
||||
api.requestPublisher(.join(request: request))
|
||||
.tryMap { response in
|
||||
try Self.validateStatusCode(response)
|
||||
|
||||
do {
|
||||
let decoded = try JSONDecoder().decode(V2VJoinResponse.self, from: response.data)
|
||||
guard !decoded.agentId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
throw V2VRepositoryError.decoding
|
||||
}
|
||||
return decoded.agentId
|
||||
} catch let error as V2VRepositoryError {
|
||||
throw error
|
||||
} catch {
|
||||
throw V2VRepositoryError.decoding
|
||||
}
|
||||
}
|
||||
.mapError { Self.mapError($0) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func leave(agentId: String) -> AnyPublisher<Void, V2VRepositoryError> {
|
||||
api.requestPublisher(.leave(agentId: agentId))
|
||||
.tryMap { response in
|
||||
try Self.validateStatusCode(response)
|
||||
if !response.data.isEmpty {
|
||||
if let text = String(data: response.data, encoding: .utf8) {
|
||||
DEBUG_LOG("[V2V] leave response: \(text)")
|
||||
}
|
||||
_ = try? JSONDecoder().decode(V2VLeaveResponse.self, from: response.data)
|
||||
}
|
||||
return ()
|
||||
}
|
||||
.mapError { Self.mapError($0) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
private static func validateStatusCode(_ response: Response) throws {
|
||||
guard (200..<300).contains(response.statusCode) else {
|
||||
throw V2VRepositoryError.business(message: parseBusinessMessage(from: response.data) ?? I18n.Common.commonError)
|
||||
}
|
||||
}
|
||||
|
||||
private static func mapError(_ error: Error) -> V2VRepositoryError {
|
||||
if let mapped = error as? V2VRepositoryError {
|
||||
return mapped
|
||||
}
|
||||
|
||||
guard let moyaError = error as? MoyaError else {
|
||||
return .network(message: I18n.Common.commonError)
|
||||
}
|
||||
|
||||
switch moyaError {
|
||||
case .statusCode(let response):
|
||||
if let message = parseBusinessMessage(from: response.data) {
|
||||
return .business(message: message)
|
||||
}
|
||||
return .business(message: I18n.Common.commonError)
|
||||
|
||||
case .objectMapping, .jsonMapping, .encodableMapping, .stringMapping, .imageMapping:
|
||||
return .decoding
|
||||
|
||||
default:
|
||||
return .network(message: I18n.Common.commonError)
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseBusinessMessage(from data: Data) -> String? {
|
||||
if let decoded = try? JSONDecoder().decode(V2VErrorResponse.self, from: data),
|
||||
let message = decoded.message?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!message.isEmpty {
|
||||
return message
|
||||
}
|
||||
|
||||
if let decoded = try? JSONDecoder().decode(ApiResponseWithoutData.self, from: data),
|
||||
let message = decoded.message?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!message.isEmpty {
|
||||
return message
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private struct V2VErrorResponse: Decodable {
|
||||
let message: String?
|
||||
}
|
||||
Reference in New Issue
Block a user