라이브룸 V2V 번역 자막 기능을 추가한다

라이브룸에서 진행자 언어와 기기 언어가 다를 때 자막 토글을 제공한다.
룸 정보 응답에 V2V 워커 토큰과 진행자 언어 코드를 포함한다.
Agora V2V 에이전트 참여와 종료 API 연동을 추가한다
This commit is contained in:
Yu Sung
2026-02-09 21:11:17 +09:00
parent 7f703024d8
commit b796f6d9c5
11 changed files with 816 additions and 2 deletions

View File

@@ -95,7 +95,9 @@ struct LiveRoomViewV2: View {
isOnNotice: viewModel.isShowNotice,
isOnMenuPan: viewModel.isShowMenuPan,
isOnSignature: viewModel.isSignatureOn,
isOnV2VCaption: viewModel.isV2VCaptionOn,
isShowMenuPanButton: !liveRoomInfo.menuPan.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
isShowV2VCaptionButton: viewModel.isV2VAvailable,
creatorId: liveRoomInfo.creatorId,
creatorNickname: liveRoomInfo.creatorNickname,
creatorProfileUrl: liveRoomInfo.creatorProfileUrl,
@@ -132,6 +134,9 @@ struct LiveRoomViewV2: View {
onClickChangeListener: {
viewModel.setListener()
},
onClickToggleV2VCaption: {
viewModel.toggleV2VCaption()
},
onClickToggleSignature: {
viewModel.isSignatureOn.toggle()
}
@@ -182,7 +187,7 @@ struct LiveRoomViewV2: View {
.onPreferenceChange(ScrollOffsetKey.self) {
viewModel.setOffset($0)
}
.padding(.bottom, 70)
.padding(.bottom, v2vCaptionBottomInset)
}
.padding(.top, 16)
@@ -308,6 +313,19 @@ struct LiveRoomViewV2: View {
}
.padding(.trailing, 13.3)
if isV2VCaptionVisible {
Text(viewModel.v2vCaptionText)
.appFont(size: 12, weight: .medium)
.foregroundColor(.white)
.lineLimit(2)
.padding(.horizontal, 12)
.padding(.vertical, 10)
.frame(maxWidth: .infinity, alignment: .center)
.background(Color.black.opacity(0.75))
.cornerRadius(10)
.padding(.horizontal, 13.3)
}
LiveRoomInputChatView {
viewModel.sendMessage(chatMessage: $0) {
viewModel.isShowingNewChat = false
@@ -316,6 +334,7 @@ struct LiveRoomViewV2: View {
return true
}
.padding(.top, isV2VCaptionVisible ? -13.3 : 0)
.padding(.bottom, 10)
}
@@ -323,7 +342,7 @@ struct LiveRoomViewV2: View {
LiveRoomNewChatView{
viewModel.isShowingNewChat = false
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
}.padding(.bottom, 70)
}.padding(.bottom, v2vCaptionBottomInset)
}
if viewModel.isSignatureOn && viewModel.signatureImageUrl.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 {
@@ -474,6 +493,7 @@ struct LiveRoomViewV2: View {
.onDisappear {
UIApplication.shared.isIdleTimerDisabled = false
NotificationCenter.default.removeObserver(self)
viewModel.stopV2VTranslationIfJoined()
viewModel.stopPeriodicPlaybackValidation()
}
@@ -744,6 +764,10 @@ struct LiveRoomViewV2: View {
if viewModel.isLoading && viewModel.liveRoomInfo == nil {
LoadingView()
}
if viewModel.isV2VLoading {
LoadingView()
}
}
.overlay(alignment: .center) {
ZStack {
@@ -916,6 +940,17 @@ struct LiveRoomViewV2: View {
}
}
private extension LiveRoomViewV2 {
var isV2VCaptionVisible: Bool {
viewModel.isV2VCaptionOn &&
!viewModel.v2vCaptionText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
var v2vCaptionBottomInset: CGFloat {
isV2VCaptionVisible ? 120 : 70
}
}
struct LiveRoomViewV2_Previews: PreviewProvider {
static var previews: some View {
LiveRoomViewV2()