Sfoglia il codice sorgente

【Android】Added voice transcription function

rulongzhang 1 anno fa
parent
commit
4ad4e44506
34 ha cambiato i file con 926 aggiunte e 24 eliminazioni
  1. 2 2
      Android/app/build.gradle
  2. 1 1
      Android/build.gradle
  3. 2 1
      Android/tuiroomkit/build.gradle
  4. 5 0
      Android/tuiroomkit/src/main/AndroidManifest.xml
  5. 2 0
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/ConferenceSessionImpl.java
  6. 2 0
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/ConferenceState.java
  7. 33 0
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/data/ASRState.java
  8. 2 1
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/data/ViewState.java
  9. 1 1
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/entity/BottomItemData.java
  10. 5 0
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/manager/ConferenceController.java
  11. 39 0
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/manager/TRTCObserver.java
  12. 38 15
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/ConferenceMainView.java
  13. 85 0
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/widget/Dialog/AIAssistantDialog.java
  14. 17 0
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/widget/SpeechToText/SpeechToTextActivity.java
  15. 77 0
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/widget/SpeechToText/SpeechToTextRecyclerView.java
  16. 175 0
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/widget/SpeechToText/SpeechToTextSubtitleView.java
  17. 74 0
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/widget/SpeechToText/SpeechToTextViewAdapter.java
  18. 31 3
      Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/viewmodel/BottomViewModel.java
  19. BIN
      Android/tuiroomkit/src/main/res/drawable-xxhdpi/tuiroomkit_ic_ai.png
  20. BIN
      Android/tuiroomkit/src/main/res/drawable-xxhdpi/tuiroomkit_ic_ai_live_record.png
  21. BIN
      Android/tuiroomkit/src/main/res/drawable-xxhdpi/tuiroomkit_ic_ai_live_subtitle.png
  22. 5 0
      Android/tuiroomkit/src/main/res/drawable/tuiroomkit_bg_speech_to_text_item_content.xml
  23. 5 0
      Android/tuiroomkit/src/main/res/drawable/tuiroomkit_bg_speech_to_text_subtitle.xml
  24. 98 0
      Android/tuiroomkit/src/main/res/layout-land/tuiroomkit_dialog_ai_assistant.xml
  25. 12 0
      Android/tuiroomkit/src/main/res/layout-land/tuiroomkit_view_room_main.xml
  26. 40 0
      Android/tuiroomkit/src/main/res/layout/tuiroomkit_activity_speech_to_text.xml
  27. 92 0
      Android/tuiroomkit/src/main/res/layout/tuiroomkit_dialog_ai_assistant.xml
  28. 1 0
      Android/tuiroomkit/src/main/res/layout/tuiroomkit_float_chat_view_display.xml
  29. 29 0
      Android/tuiroomkit/src/main/res/layout/tuiroomkit_item_speech_to_text_float_subtitle.xml
  30. 12 0
      Android/tuiroomkit/src/main/res/layout/tuiroomkit_view_room_main.xml
  31. 26 0
      Android/tuiroomkit/src/main/res/layout/tuiroomkit_view_speech_to_text_item.xml
  32. 5 0
      Android/tuiroomkit/src/main/res/values-ar/strings.xml
  33. 5 0
      Android/tuiroomkit/src/main/res/values-zh/strings.xml
  34. 5 0
      Android/tuiroomkit/src/main/res/values/strings.xml

+ 2 - 2
Android/app/build.gradle

@@ -8,8 +8,8 @@ android {
 
     defaultConfig {
         applicationId 'com.tencent.liteav.tuiroom'
-        versionName "2.6.2"
-        versionCode 262
+        versionName "2.6.6"
+        versionCode 266
         minSdkVersion 19
         targetSdkVersion 30
         multiDexEnabled true

+ 1 - 1
Android/build.gradle

@@ -35,6 +35,6 @@ task clean(type: Delete) {
 
 ext {
     liteavSdk = "com.tencent.liteav:LiteAVSDK_TRTC:12.0.0.14681"
-    roomEngineSdk = "io.trtc.uikit:rtc_room_engine:2.6.0.63"
+    roomEngineSdk = "io.trtc.uikit:rtc_room_engine:2.6.6.73"
     imSdk = "com.tencent.imsdk:imsdk-plus:8.1.6103"
 }

+ 2 - 1
Android/tuiroomkit/build.gradle

@@ -47,10 +47,11 @@ dependencies {
     implementation 'com.google.android.material:material:1.4.0'
     implementation "androidx.constraintlayout:constraintlayout:1.1.3"
     implementation 'androidx.core:core:1.0.2'
+    implementation 'com.tencentcloudapi:tencentcloud-sdk-java:3.1.1113'
 
     implementation project(':timcommon')
 
-    api rootProject.getProperties().containsKey("roomEngineSdk") ? rootProject.ext.roomEngineSdk : "io.trtc.uikit:rtc_room_engine:2.6.0.63"
+    api rootProject.getProperties().containsKey("roomEngineSdk") ? rootProject.ext.roomEngineSdk : "io.trtc.uikit:rtc_room_engine:2.6.6.73"
     implementation rootProject.getProperties().containsKey("liteavSdk") ? rootProject.ext.liteavSdk : "com.tencent.liteav:LiteAVSDK_TRTC:12.0.0.14681"
     if (projects.contains("tuicore")) {
         api project(':tuicore')

+ 5 - 0
Android/tuiroomkit/src/main/AndroidManifest.xml

@@ -95,6 +95,11 @@
             android:configChanges="orientation|screenSize"
             android:launchMode="singleTask"
             android:theme="@style/Theme.AppCompat.NoActionBar" />
+        <activity
+            android:name="com.tencent.cloud.tuikit.roomkit.view.page.widget.SpeechToText.SpeechToTextActivity"
+            android:configChanges="orientation|screenSize"
+            android:launchMode="singleTask"
+            android:theme="@style/Theme.AppCompat.NoActionBar" />
         <service
             android:name="com.tencent.cloud.tuikit.roomkit.common.KeepAliveService"
             android:enabled="true"

+ 2 - 0
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/ConferenceSessionImpl.java

@@ -12,6 +12,8 @@ public class ConferenceSessionImpl extends ConferenceSession {
     private static ConferenceSessionImpl     sInstance;
     private final  ConferenceObserverManager mConferenceObserverManager = new ConferenceObserverManager();
 
+    public boolean isShowAISpeechToTextButton = false;
+
     private ConferenceSessionImpl() {}
 
     public static ConferenceSessionImpl sharedInstance() {

+ 2 - 0
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/ConferenceState.java

@@ -13,6 +13,7 @@ import android.text.TextUtils;
 import android.util.Log;
 
 import com.tencent.cloud.tuikit.engine.room.TUIRoomDefine;
+import com.tencent.cloud.tuikit.roomkit.model.data.ASRState;
 import com.tencent.cloud.tuikit.roomkit.model.data.MediaState;
 import com.tencent.cloud.tuikit.roomkit.model.data.InvitationState;
 import com.tencent.cloud.tuikit.roomkit.model.data.RoomState;
@@ -40,6 +41,7 @@ public class ConferenceState {
     public UserState       userState       = new UserState();
     public MediaState      mediaState      = new MediaState();
     public InvitationState invitationState = new InvitationState();
+    public ASRState        asrState        = new ASRState();
 
     public TUIRoomDefine.RoomInfo roomInfo;
     public UserModel              userModel;

+ 33 - 0
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/data/ASRState.java

@@ -0,0 +1,33 @@
+package com.tencent.cloud.tuikit.roomkit.model.data;
+
+import android.text.TextUtils;
+
+import androidx.annotation.Nullable;
+
+import com.tencent.cloud.tuikit.roomkit.common.livedata.LiveListData;
+
+public class ASRState {
+    public LiveListData<SpeechToText> speechToTexts = new LiveListData<>();
+
+    public static class SpeechToText {
+        public String roundId     = "";
+        public String userId      = "";
+        public String userName    = "";
+        public String avatarUrl   = "";
+        public String text        = "";
+        public long   startTimeMs = 0L;
+        public long   endTimeMs   = 0L;
+        public String type        = ASR_TYPE_SUBTITLE;
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (obj instanceof SpeechToText) {
+                return TextUtils.equals(this.roundId, ((SpeechToText) obj).roundId);
+            }
+            return false;
+        }
+    }
+
+    public static final String ASR_TYPE_SUBTITLE      = "subtitle";
+    public static final String ASR_TYPE_TRANSCRIPTION = "transcription";
+}

+ 2 - 1
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/data/ViewState.java

@@ -16,7 +16,8 @@ public class ViewState {
     public LiveData<RoomProcess> roomProcess           = new LiveData<>(RoomProcess.NONE);
     public LiveData<Long>        enterRoomTimeFromBoot = new LiveData<>(0L);
 
-    public LiveData<Boolean> isInvitationPending = new LiveData<>(false);
+    public LiveData<Boolean> isInvitationPending           = new LiveData<>(false);
+    public LiveData<Boolean> isSpeechToTextSubTitleShowing = new LiveData<>(false);
 
     public void addPendingTakeSeatRequest(String requestId) {
         pendingTakeSeatRequests.add(requestId);

+ 1 - 1
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/entity/BottomItemData.java

@@ -3,7 +3,6 @@ package com.tencent.cloud.tuikit.roomkit.model.entity;
 import android.view.View;
 
 import androidx.annotation.DrawableRes;
-import androidx.annotation.StringRes;
 
 public class BottomItemData {
     private boolean enable;
@@ -140,6 +139,7 @@ public class BottomItemData {
         INVITE,
         MINIMIZE,
         RECORD,
+        AI,
         SETTING
     }
 }

+ 5 - 0
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/manager/ConferenceController.java

@@ -53,6 +53,7 @@ import com.tencent.cloud.tuikit.roomkit.model.controller.MediaController;
 import com.tencent.cloud.tuikit.roomkit.model.controller.RoomController;
 import com.tencent.cloud.tuikit.roomkit.model.controller.UserController;
 import com.tencent.cloud.tuikit.roomkit.model.controller.ViewController;
+import com.tencent.cloud.tuikit.roomkit.model.data.ASRState;
 import com.tencent.cloud.tuikit.roomkit.model.data.InvitationState;
 import com.tencent.cloud.tuikit.roomkit.model.data.MediaState;
 import com.tencent.cloud.tuikit.roomkit.model.data.RoomState;
@@ -159,6 +160,10 @@ public class ConferenceController {
         return mConferenceState.invitationState;
     }
 
+    public ASRState getASRState() {
+        return mConferenceState.asrState;
+    }
+
     public void responseRemoteRequest(TUIRoomDefine.RequestAction requestAction, String requestId, boolean agree,
                                       TUIRoomDefine.ActionCallback callback) {
         if ((requestAction == REQUEST_TO_OPEN_REMOTE_MICROPHONE || requestAction == REQUEST_TO_OPEN_REMOTE_CAMERA)

+ 39 - 0
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/model/manager/TRTCObserver.java

@@ -5,15 +5,22 @@ import static com.tencent.cloud.tuikit.roomkit.model.ConferenceEventConstant.KEY
 import static com.tencent.cloud.tuikit.roomkit.model.ConferenceEventConstant.KEY_CONFERENCE_FINISHED;
 import static com.tencent.cloud.tuikit.roomkit.model.ConferenceEventConstant.KEY_REASON;
 
+import android.text.TextUtils;
 import android.util.Log;
 
+import com.tencent.cloud.tuikit.roomkit.common.livedata.LiveListData;
 import com.tencent.cloud.tuikit.roomkit.model.ConferenceEventCenter;
 import com.tencent.cloud.tuikit.roomkit.model.ConferenceEventConstant;
 import com.tencent.cloud.tuikit.roomkit.model.ConferenceState;
+import com.tencent.cloud.tuikit.roomkit.model.data.ASRState;
+import com.tencent.cloud.tuikit.roomkit.model.data.UserState;
 import com.tencent.qcloud.tuicore.TUICore;
 import com.tencent.trtc.TRTCCloudListener;
 import com.tencent.trtc.TRTCStatistics;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -37,6 +44,38 @@ public class TRTCObserver extends TRTCCloudListener {
         TUICore.notifyEvent(KEY_CONFERENCE, KEY_CONFERENCE_FINISHED, param);
     }
 
+    @Override
+    public void onRecvCustomCmdMsg(String userId, int cmdID, int seq, byte[] message) {
+        if (cmdID != 1 || message == null) {
+            return;
+        }
+        ASRState.SpeechToText speechToText = new ASRState.SpeechToText();
+        try {
+            JSONObject data = new JSONObject(new String(message));
+            speechToText.roundId = data.getString("roundid");
+            speechToText.userId = data.getString("userid");
+            speechToText.text = data.getString("text");
+            speechToText.startTimeMs = data.getLong("start_ms_ts");
+            speechToText.endTimeMs = data.getLong("end_ms_ts");
+            speechToText.type = data.getString("type");
+        } catch (JSONException e) {
+            return;
+        }
+        UserState.UserInfo userInfo = ConferenceController.sharedInstance().getUserState().allUsers.find(new UserState.UserInfo(speechToText.userId));
+        if (userInfo != null && !TextUtils.isEmpty(userInfo.userName)) {
+            speechToText.userName = userInfo.userName;
+        }
+        if (userInfo != null && !TextUtils.isEmpty(userInfo.avatarUrl)) {
+            speechToText.avatarUrl = userInfo.avatarUrl;
+        }
+        LiveListData<ASRState.SpeechToText> speechToTexts = ConferenceController.sharedInstance().getASRState().speechToTexts;
+        if (speechToTexts.contains(speechToText)) {
+            speechToTexts.change(speechToText);
+        } else {
+            speechToTexts.add(speechToText);
+        }
+    }
+
     @Override
     public void onConnectionLost() {
         Log.d(TAG, "onConnectionLost");

+ 38 - 15
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/ConferenceMainView.java

@@ -54,6 +54,7 @@ import com.tencent.cloud.tuikit.roomkit.view.page.widget.RaiseHandControlPanel.R
 import com.tencent.cloud.tuikit.roomkit.view.page.widget.ScheduleConference.SelectScheduleParticipant.ConferenceParticipants;
 import com.tencent.cloud.tuikit.roomkit.view.page.widget.ScheduleConference.SelectScheduleParticipant.ParticipantSelector;
 import com.tencent.cloud.tuikit.roomkit.view.page.widget.ScheduleConference.SelectScheduleParticipant.User;
+import com.tencent.cloud.tuikit.roomkit.view.page.widget.SpeechToText.SpeechToTextSubtitleView;
 import com.tencent.cloud.tuikit.roomkit.view.page.widget.TopNavigationBar.TopView;
 import com.tencent.cloud.tuikit.roomkit.view.page.widget.TransferOwnerControlPanel.TransferMasterPanel;
 import com.tencent.cloud.tuikit.roomkit.view.page.widget.UserControlPanel.UserListPanel;
@@ -74,19 +75,21 @@ public class ConferenceMainView extends RelativeLayout {
     private static final int ROOM_BARS_FIRST_SHOW_TIME_MS        = 6 * 1000;
     private static final int CONFIRM_DIALOG_AUTO_DISMISS_SECONDS = 5;
 
-    private Context           mContext;
-    private View              mFloatingWindow;
-    private TUIVideoSeatView  mVideoSeatView;
-    private FloatChatView     mFloatChatView;
-    private Button            mBtnStopScreenShare;
-    private FrameLayout       mLayoutTopView;
-    private FrameLayout       mLayoutVideoSeat;
-    private FrameLayout       mLayoutLocalAudio;
-    private FrameLayout       mLayoutFloatChatView;
-    private View              mLayoutScreenCaptureGroup;
-    private FrameLayout       mLayoutBottomView;
-    private BottomLayout      mBottomLayout;
-    private RoomMainViewModel mViewModel;
+    private Context                  mContext;
+    private View                     mFloatingWindow;
+    private TUIVideoSeatView         mVideoSeatView;
+    private FloatChatView            mFloatChatView;
+    private SpeechToTextSubtitleView mSpeechToTextSubtitleView;
+    private Button                   mBtnStopScreenShare;
+    private FrameLayout              mLayoutTopView;
+    private FrameLayout              mLayoutVideoSeat;
+    private FrameLayout              mLayoutLocalAudio;
+    private FrameLayout              mLayoutFloatChatView;
+    private FrameLayout              mLayoutSpeechToTextSubtitle;
+    private View                     mLayoutScreenCaptureGroup;
+    private FrameLayout              mLayoutBottomView;
+    private BottomLayout             mBottomLayout;
+    private RoomMainViewModel        mViewModel;
 
     private final ParticipantSelector mParticipantSelector = new ParticipantSelector();
 
@@ -101,7 +104,8 @@ public class ConferenceMainView extends RelativeLayout {
     private float           mTouchDownPointX;
     private float           mTouchDownPointY;
 
-    private final Observer<String> mRoomIdObserver = this::updateRoomId;
+    private final Observer<String>  mRoomIdObserver     = this::updateRoomId;
+    private final Observer<Boolean> mAISubtitleObserver = this::updateSubtitleView;
 
     public ConferenceMainView(Context context) {
         this(context, null);
@@ -243,6 +247,7 @@ public class ConferenceMainView extends RelativeLayout {
         mContext = context;
         mViewModel = new RoomMainViewModel(mContext, this);
         mVideoSeatView = new TUIVideoSeatView(mContext);
+        mSpeechToTextSubtitleView = new SpeechToTextSubtitleView(mContext);
         mVideoSeatView.setViewClickListener(this::onClick);
         initView();
     }
@@ -267,8 +272,10 @@ public class ConferenceMainView extends RelativeLayout {
             textWaterMarkView.setText(mViewModel.getWaterMakText());
             mLayoutVideoSeat.addView(textWaterMarkView);
         }
+        mLayoutFloatChatView = findViewById(R.id.tuiroomkit_float_chat_view_container);
         initScreenCaptureView();
         initFloatChatView();
+        initSpeechToTextSubtitleView();
 
         mBottomLayout = new BottomLayout(mContext);
         mBottomLayout.setExpandStateListener(this::onExpandStateChanged);
@@ -286,6 +293,7 @@ public class ConferenceMainView extends RelativeLayout {
         if (ConferenceController.sharedInstance().getConferenceState().videoModel.isScreenSharing()) {
             onScreenShareStarted();
         }
+        updateSubtitleView(ConferenceController.sharedInstance().getViewState().isSpeechToTextSubTitleShowing.get());
         showRoomBars();
         if (mIsBottomViewExpanded) {
             mBottomLayout.expandView();
@@ -475,6 +483,7 @@ public class ConferenceMainView extends RelativeLayout {
         Configuration curConfig = mContext.getResources().getConfiguration();
         ConferenceController.sharedInstance().getViewController().updateScreenOrientation(curConfig);
         ConferenceController.sharedInstance().getRoomState().roomId.observe(mRoomIdObserver);
+        ConferenceController.sharedInstance().getViewState().isSpeechToTextSubTitleShowing.observe(mAISubtitleObserver);
     }
 
     @Override
@@ -489,6 +498,7 @@ public class ConferenceMainView extends RelativeLayout {
             mFloatChatView.destroy();
         }
         ConferenceController.sharedInstance().getRoomState().roomId.removeObserver(mRoomIdObserver);
+        ConferenceController.sharedInstance().getViewState().isSpeechToTextSubTitleShowing.removeObserver(mAISubtitleObserver);
     }
 
     private void updateRoomId(String roomId) {
@@ -500,11 +510,15 @@ public class ConferenceMainView extends RelativeLayout {
         initFloatChatView();
     }
 
+    private void updateSubtitleView(boolean isShowSubtitle) {
+        mLayoutFloatChatView.setVisibility(isShowSubtitle ? INVISIBLE : VISIBLE);
+        mLayoutSpeechToTextSubtitle.setVisibility(isShowSubtitle ? VISIBLE : INVISIBLE);
+    }
+
     private void initFloatChatView() {
         if (mFloatChatView == null) {
             return;
         }
-        mLayoutFloatChatView = findViewById(R.id.tuiroomkit_float_chat_view_container);
         ViewParent floatChatViewParent = mFloatChatView.getParent();
         if (floatChatViewParent instanceof ViewGroup) {
             ((ViewGroup) floatChatViewParent).removeView(mFloatChatView);
@@ -513,6 +527,15 @@ public class ConferenceMainView extends RelativeLayout {
         mLayoutFloatChatView.addView(mFloatChatView);
     }
 
+    private void initSpeechToTextSubtitleView() {
+        mLayoutSpeechToTextSubtitle = findViewById(R.id.tuiroomkit_speech_to_text_subtitle_container);
+        ViewParent parent = mSpeechToTextSubtitleView.getParent();
+        if (parent instanceof ViewGroup) {
+            ((ViewGroup) parent).removeView(mSpeechToTextSubtitleView);
+        }
+        mLayoutSpeechToTextSubtitle.addView(mSpeechToTextSubtitleView);
+    }
+
     public void onScreenShareStarted() {
         mLayoutScreenCaptureGroup.setVisibility(View.VISIBLE);
 

+ 85 - 0
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/widget/Dialog/AIAssistantDialog.java

@@ -0,0 +1,85 @@
+package com.tencent.cloud.tuikit.roomkit.view.page.widget.Dialog;
+
+import static com.tencent.cloud.tuikit.roomkit.model.ConferenceEventCenter.RoomKitUIEvent.CONFIGURATION_CHANGE;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.text.TextUtils;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.tencent.cloud.tuikit.roomkit.R;
+import com.tencent.cloud.tuikit.roomkit.model.ConferenceEventCenter;
+import com.tencent.cloud.tuikit.roomkit.model.ConferenceEventConstant;
+import com.tencent.cloud.tuikit.roomkit.model.manager.ConferenceController;
+import com.tencent.cloud.tuikit.roomkit.view.component.BaseBottomDialog;
+import com.tencent.cloud.tuikit.roomkit.view.page.widget.SpeechToText.SpeechToTextActivity;
+import com.trtc.tuikit.common.livedata.Observer;
+
+import java.util.Map;
+
+public class AIAssistantDialog extends BaseBottomDialog implements ConferenceEventCenter.RoomKitUIEventResponder {
+
+    private       RelativeLayout mLayoutAILiveSubtitle;
+    private       RelativeLayout mLayoutAILiveRecord;
+    private       TextView       mTvSubtitleName;
+    private final Context        mContext;
+
+    private final Observer<Boolean> mSubtitleObserver = this::updateSubtitleView;
+
+    public AIAssistantDialog(@NonNull Context context) {
+        super(context);
+        mContext = context;
+        ConferenceEventCenter.getInstance().subscribeUIEvent(CONFIGURATION_CHANGE, this);
+    }
+
+    @Override
+    public void dismiss() {
+        super.dismiss();
+        ConferenceEventCenter.getInstance().unsubscribeUIEvent(CONFIGURATION_CHANGE, this);
+        ConferenceController.sharedInstance().getViewState().isSpeechToTextSubTitleShowing.removeObserver(mSubtitleObserver);
+    }
+
+    @Override
+    protected int getLayoutId() {
+        return R.layout.tuiroomkit_dialog_ai_assistant;
+    }
+
+    @Override
+    protected void initView() {
+        mLayoutAILiveSubtitle = findViewById(R.id.tuiroomkit_rl_ai_live_subtitle);
+        mLayoutAILiveSubtitle.setOnClickListener(view -> {
+            boolean isShowSubtitle = ConferenceController.sharedInstance().getViewState().isSpeechToTextSubTitleShowing.get();
+            ConferenceController.sharedInstance().getViewState().isSpeechToTextSubTitleShowing.set(!isShowSubtitle);
+            dismiss();
+        });
+
+        mLayoutAILiveRecord = findViewById(R.id.tuiroomkit_rl_ai_live_recording);
+        mLayoutAILiveRecord.setOnClickListener(view -> {
+            Intent intent = new Intent(mContext, SpeechToTextActivity.class);
+            mContext.startActivity(intent);
+            dismiss();
+        });
+
+        mTvSubtitleName = findViewById(R.id.tuiroomkit_iv_ai_live_subtitle_name);
+        ConferenceController.sharedInstance().getViewState().isSpeechToTextSubTitleShowing.observe(mSubtitleObserver);
+    }
+
+    @Override
+    public void onNotifyUIEvent(String key, Map<String, Object> params) {
+        if (TextUtils.equals(key, CONFIGURATION_CHANGE)) {
+            if (params == null || !isShowing()) {
+                return;
+            }
+            Configuration configuration = (Configuration) params.get(ConferenceEventConstant.KEY_CONFIGURATION);
+            changeConfiguration(configuration);
+        }
+    }
+
+    private void updateSubtitleView(boolean isShowSubtitle) {
+        mTvSubtitleName.setText(isShowSubtitle ? R.string.tuiroomkit_ai_close_live_subtitle : R.string.tuiroomkit_ai_open_live_subtitle);
+    }
+}

+ 17 - 0
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/widget/SpeechToText/SpeechToTextActivity.java

@@ -0,0 +1,17 @@
+package com.tencent.cloud.tuikit.roomkit.view.page.widget.SpeechToText;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.tencent.cloud.tuikit.roomkit.R;
+
+public class SpeechToTextActivity extends AppCompatActivity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.tuiroomkit_activity_speech_to_text);
+        findViewById(R.id.tuiroomkit_speech_to_text_back).setOnClickListener(v -> finish());
+    }
+}

+ 77 - 0
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/widget/SpeechToText/SpeechToTextRecyclerView.java

@@ -0,0 +1,77 @@
+package com.tencent.cloud.tuikit.roomkit.view.page.widget.SpeechToText;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.tencent.cloud.tuikit.roomkit.common.livedata.LiveListObserver;
+import com.tencent.cloud.tuikit.roomkit.model.data.ASRState;
+import com.tencent.cloud.tuikit.roomkit.model.manager.ConferenceController;
+
+import java.util.List;
+
+public class SpeechToTextRecyclerView extends RecyclerView {
+    private final SpeechToTextViewAdapter mTextAdapter = new SpeechToTextViewAdapter(getContext());
+    private final LinearLayoutManager     mLayoutManager;
+
+    private final LiveListObserver<ASRState.SpeechToText> mTextObserver = new LiveListObserver<ASRState.SpeechToText>() {
+        @Override
+        public void onDataChanged(List<ASRState.SpeechToText> list) {
+            mTextAdapter.setDataList(list);
+            scrollToPosition(list.size() - 1);
+        }
+
+        @Override
+        public void onItemChanged(int position, ASRState.SpeechToText item) {
+            mTextAdapter.notifyItemChanged(position);
+        }
+
+        @Override
+        public void onItemInserted(int position, ASRState.SpeechToText item) {
+            mTextAdapter.notifyItemInserted(position);
+            scrollToLatestIfNeeded();
+        }
+    };
+
+    public SpeechToTextRecyclerView(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public SpeechToTextRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        setAdapter(mTextAdapter);
+        mLayoutManager = new LinearLayoutManager(context);
+        setLayoutManager(mLayoutManager);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        ConferenceController.sharedInstance().getASRState().speechToTexts.observe(mTextObserver);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        ConferenceController.sharedInstance().getASRState().speechToTexts.removeObserver(mTextObserver);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        scrollToLatestIfNeeded();
+    }
+
+    private void scrollToLatestIfNeeded() {
+        int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
+        int lastItemPosition = mTextAdapter.getItemCount() - 1;
+        if (lastVisibleItemPosition >= lastItemPosition - 1) {
+            scrollToPosition(lastItemPosition);
+        }
+    }
+}

+ 175 - 0
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/widget/SpeechToText/SpeechToTextSubtitleView.java

@@ -0,0 +1,175 @@
+package com.tencent.cloud.tuikit.roomkit.view.page.widget.SpeechToText;
+
+import static com.tencent.cloud.tuikit.roomkit.model.data.ASRState.ASR_TYPE_TRANSCRIPTION;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.tencent.cloud.tuikit.roomkit.R;
+import com.tencent.cloud.tuikit.roomkit.common.livedata.LiveListObserver;
+import com.tencent.cloud.tuikit.roomkit.model.data.ASRState;
+import com.tencent.cloud.tuikit.roomkit.model.manager.ConferenceController;
+
+import java.util.LinkedList;
+
+public class SpeechToTextSubtitleView extends ScrollView {
+    private static final String TAG = "SpeechToTextSubtitleView";
+
+    private static final int MAX_ITEM_COUNT           = 3;
+    private static final int MESSAGE_SHOW_TIME_MS     = 5 * 1000;
+    private static final int MESSAGE_MIN_SHOW_TIME_MS = 5 * 100;
+
+    private final LinkedList<MessageHolder> mMessageHolders    = new LinkedList<>();
+    private final LinkedList<MessageHolder> mMessageHolderPool = new LinkedList<>();
+
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+    private final LinearLayout mRootView;
+
+    private final LiveListObserver<ASRState.SpeechToText> mTextObserver = new LiveListObserver<ASRState.SpeechToText>() {
+        @Override
+        public void onItemChanged(int position, ASRState.SpeechToText item) {
+            if (TextUtils.equals(ASR_TYPE_TRANSCRIPTION, item.type)) {
+                addMessage(item);
+            }
+        }
+    };
+
+    public SpeechToTextSubtitleView(Context context) {
+        this(context, null);
+    }
+
+    public SpeechToTextSubtitleView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        setVerticalScrollBarEnabled(false);
+        mRootView = new LinearLayout(context);
+        mRootView.setOrientation(LinearLayout.VERTICAL);
+        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+        addView(mRootView, layoutParams);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        int count = mMessageHolders.size();
+        for (int i = 0; i < count; i++) {
+            MessageHolder holder = mMessageHolders.get(i);
+            long showTime = MESSAGE_SHOW_TIME_MS - (SystemClock.elapsedRealtime() - holder.receivedTimeMs);
+            showTime = Math.max(MESSAGE_MIN_SHOW_TIME_MS, showTime);
+            mMainHandler.postDelayed(holder.dismissRun, showTime);
+        }
+        ConferenceController.sharedInstance().getASRState().speechToTexts.observe(mTextObserver);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        ConferenceController.sharedInstance().getASRState().speechToTexts.removeObserver(mTextObserver);
+        for (MessageHolder item : mMessageHolders) {
+            mMainHandler.removeCallbacks(item.dismissRun);
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return false;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return false;
+    }
+
+    private void addMessage(ASRState.SpeechToText message) {
+        if (message == null) {
+            return;
+        }
+        if (mMessageHolders.size() >= MAX_ITEM_COUNT) {
+            recycleFirstItem();
+        }
+        addItem(message);
+    }
+
+    private void recycleFirstItem() {
+        removeFirstView();
+        recycleMessageHolder();
+    }
+
+    private void addItem(ASRState.SpeechToText message) {
+        MessageHolder messageHolder = obtainMessageHolder();
+        messageHolder.userView.setText(message.userName + ":");
+        messageHolder.messageView.setText(message.text);
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.WRAP_CONTENT,
+                LinearLayout.LayoutParams.WRAP_CONTENT);
+        params.setMargins(0,16, 0 , 0);
+        messageHolder.parent.setLayoutParams(params);
+        mRootView.addView(messageHolder.parent);
+        post(new Runnable() {
+            @Override
+            public void run() {
+                fullScroll(View.FOCUS_DOWN);
+            }
+        });
+    }
+
+    private MessageHolder obtainMessageHolder() {
+        MessageHolder messageHolder;
+        if (mMessageHolderPool.isEmpty()) {
+            messageHolder = new MessageHolder();
+            messageHolder.parent = LayoutInflater.from(getContext()).inflate(R.layout.tuiroomkit_item_speech_to_text_float_subtitle, null);
+            messageHolder.userView = messageHolder.parent.findViewById(R.id.tuiroomkit_tv_speech_to_text_float_subtitle_user);
+            messageHolder.messageView = messageHolder.parent.findViewById(R.id.tuiroomkit_tv_speech_to_text_float_subtitle_msg);
+            messageHolder.dismissRun = this::recycleFirstItem;
+        } else {
+            messageHolder = mMessageHolderPool.remove(0);
+        }
+        messageHolder.receivedTimeMs = SystemClock.elapsedRealtime();
+        mMessageHolders.add(messageHolder);
+        mMainHandler.postDelayed(messageHolder.dismissRun, MESSAGE_SHOW_TIME_MS);
+        return messageHolder;
+    }
+
+    private void recycleMessageHolder() {
+        if (mMessageHolders.isEmpty()) {
+            Log.w(TAG, "recycleMessageHolder at no child");
+            return;
+        }
+        MessageHolder messageHolder = mMessageHolders.remove(0);
+        mMainHandler.removeCallbacks(messageHolder.dismissRun);
+        mMessageHolderPool.add(messageHolder);
+    }
+
+    private void removeFirstView() {
+        int count = mRootView.getChildCount();
+        if (count <= 0) {
+            Log.w(TAG, "removeFirstView at no child");
+            return;
+        }
+        mRootView.removeViewAt(0);
+    }
+
+    private static class MessageHolder {
+        View     parent;
+        TextView userView;
+        TextView messageView;
+        long     receivedTimeMs;
+        Runnable dismissRun;
+    }
+}

+ 74 - 0
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/view/page/widget/SpeechToText/SpeechToTextViewAdapter.java

@@ -0,0 +1,74 @@
+package com.tencent.cloud.tuikit.roomkit.view.page.widget.SpeechToText;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.tencent.cloud.tuikit.roomkit.R;
+import com.tencent.cloud.tuikit.roomkit.model.data.ASRState;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+public class SpeechToTextViewAdapter extends RecyclerView.Adapter {
+    private List<ASRState.SpeechToText> mTextList = new LinkedList<>();
+
+    private final Context mContext;
+
+    public SpeechToTextViewAdapter(Context context) {
+        mContext = context;
+    }
+
+    public void setDataList(List<ASRState.SpeechToText> list) {
+        mTextList = list;
+    }
+
+    @NonNull
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(mContext).inflate(R.layout.tuiroomkit_view_speech_to_text_item, parent, false);
+        return new TextViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+        ASRState.SpeechToText speechToText = mTextList.get(position);
+        ((TextViewHolder) holder).bindData(speechToText);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mTextList.size();
+    }
+
+    static class TextViewHolder extends RecyclerView.ViewHolder {
+        private final TextView textTitle;
+        private final TextView textContent;
+
+        public TextViewHolder(View itemView) {
+            super(itemView);
+            textTitle = itemView.findViewById(R.id.tuiroomkit_speech_to_text_item_title);
+            textContent = itemView.findViewById(R.id.tuiroomkit_speech_to_text_item_content);
+        }
+
+        public void bindData(ASRState.SpeechToText speechToText) {
+            textTitle.setText(parseTitle(speechToText));
+            textContent.setText(speechToText.text);
+        }
+
+        public static String parseTitle(ASRState.SpeechToText speechToText) {
+            SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss", Locale.getDefault());
+            Date date = new Date(speechToText.startTimeMs);
+            return speechToText.userName + "  " + sdf.format(date);
+        }
+    }
+}
+

+ 31 - 3
Android/tuiroomkit/src/main/java/com/tencent/cloud/tuikit/roomkit/viewmodel/BottomViewModel.java

@@ -22,18 +22,20 @@ import androidx.appcompat.app.AppCompatActivity;
 import com.tencent.cloud.tuikit.engine.common.TUICommonDefine;
 import com.tencent.cloud.tuikit.engine.room.TUIRoomDefine;
 import com.tencent.cloud.tuikit.roomkit.R;
+import com.tencent.cloud.tuikit.roomkit.common.utils.DrawOverlaysPermissionUtil;
+import com.tencent.cloud.tuikit.roomkit.common.utils.IntentUtils;
+import com.tencent.cloud.tuikit.roomkit.common.utils.RoomToast;
 import com.tencent.cloud.tuikit.roomkit.model.ConferenceEventCenter;
+import com.tencent.cloud.tuikit.roomkit.model.ConferenceSessionImpl;
 import com.tencent.cloud.tuikit.roomkit.model.ConferenceState;
 import com.tencent.cloud.tuikit.roomkit.model.entity.BottomItemData;
 import com.tencent.cloud.tuikit.roomkit.model.entity.BottomSelectItemData;
 import com.tencent.cloud.tuikit.roomkit.model.entity.UserEntity;
 import com.tencent.cloud.tuikit.roomkit.model.manager.ConferenceController;
-import com.tencent.cloud.tuikit.roomkit.common.utils.DrawOverlaysPermissionUtil;
-import com.tencent.cloud.tuikit.roomkit.common.utils.IntentUtils;
-import com.tencent.cloud.tuikit.roomkit.common.utils.RoomToast;
 import com.tencent.cloud.tuikit.roomkit.view.component.BaseDialogFragment;
 import com.tencent.cloud.tuikit.roomkit.view.page.widget.BottomNavigationBar.BottomView;
 import com.tencent.cloud.tuikit.roomkit.view.page.widget.Chat.ChatActivity;
+import com.tencent.cloud.tuikit.roomkit.view.page.widget.Dialog.AIAssistantDialog;
 import com.tencent.qcloud.tuicore.TUIConstants;
 import com.tencent.qcloud.tuicore.TUICore;
 import com.tencent.qcloud.tuicore.interfaces.ITUIService;
@@ -134,6 +136,7 @@ public class BottomViewModel implements ConferenceEventCenter.RoomEngineEventRes
         addChatItemIfNeeded(itemDataList);
         addInviteItemIfNeeded(itemDataList);
         addFloatItemIfNeeded(itemDataList);
+        addAIItemIfNeeded(itemDataList);
         addSettingsItemIfNeeded(itemDataList);
         return itemDataList;
     }
@@ -198,6 +201,13 @@ public class BottomViewModel implements ConferenceEventCenter.RoomEngineEventRes
         itemDataList.add(createFloatItem());
     }
 
+    private void addAIItemIfNeeded(List<BottomItemData> itemDataList) {
+        if (!ConferenceSessionImpl.sharedInstance().isShowAISpeechToTextButton) {
+            return;
+        }
+        itemDataList.add(createAIItem());
+    }
+
     private void addSettingsItemIfNeeded(List<BottomItemData> itemDataList) {
         itemDataList.add(createSettingItem());
     }
@@ -775,6 +785,24 @@ public class BottomViewModel implements ConferenceEventCenter.RoomEngineEventRes
         return recordItemData;
     }
 
+    private BottomItemData createAIItem() {
+        BottomItemData aiItemData = new BottomItemData();
+        aiItemData.setType(BottomItemData.Type.AI);
+        aiItemData.setEnable(true);
+        aiItemData.setIconId(R.drawable.tuiroomkit_ic_ai);
+        aiItemData.setBackground(R.drawable.tuiroomkit_bg_bottom_item_black);
+        aiItemData.setName(mContext.getString(R.string.tuiroomkit_ai_tool));
+        aiItemData.setOnItemClickListener(new BottomItemData.OnItemClickListener() {
+            @Override
+            public void onItemClick() {
+                ConferenceEventCenter.getInstance().notifyUIEvent(BAR_SHOW_TIME_RECOUNT, null);
+                AIAssistantDialog dialog = new AIAssistantDialog(mContext);
+                dialog.show();
+            }
+        });
+        return aiItemData;
+    }
+
     private BottomItemData createSettingItem() {
         BottomItemData setttingItemData = new BottomItemData();
         setttingItemData.setType(BottomItemData.Type.SETTING);

BIN
Android/tuiroomkit/src/main/res/drawable-xxhdpi/tuiroomkit_ic_ai.png


BIN
Android/tuiroomkit/src/main/res/drawable-xxhdpi/tuiroomkit_ic_ai_live_record.png


BIN
Android/tuiroomkit/src/main/res/drawable-xxhdpi/tuiroomkit_ic_ai_live_subtitle.png


+ 5 - 0
Android/tuiroomkit/src/main/res/drawable/tuiroomkit_bg_speech_to_text_item_content.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#2A2D38" />
+    <corners android:radius="8dp" />
+</shape>

+ 5 - 0
Android/tuiroomkit/src/main/res/drawable/tuiroomkit_bg_speech_to_text_subtitle.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#BF22262E" />
+    <corners android:radius="10dp" />
+</shape>

+ 98 - 0
Android/tuiroomkit/src/main/res/layout-land/tuiroomkit_dialog_ai_assistant.xml

@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/tuiroomkit_bg_bottom_dialog_black_portrait"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp"
+    android:paddingBottom="30dp">
+
+    <View
+        android:id="@+id/tuiroomkit_dialog_ai_hide"
+        android:layout_width="3dp"
+        android:layout_height="24dp"
+        android:layout_gravity="center"
+        android:layout_marginStart="12dp"
+        android:background="@drawable/tuiroomkit_bottom_dialog_hide_land"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <RelativeLayout
+        android:id="@+id/tuiroomkit_rl_ai_live_subtitle"
+        android:layout_width="0dp"
+        android:layout_height="56dp"
+        android:layout_marginStart="12dp"
+        android:layout_marginTop="12dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/tuiroomkit_dialog_ai_hide"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:layout_editor_absoluteX="16dp">
+
+        <ImageView
+            android:id="@+id/tuiroomkit_iv_ai_live_subtitle_ic"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_centerVertical="true"
+            android:clickable="false"
+            android:src="@drawable/tuiroomkit_ic_ai_live_subtitle" />
+
+        <TextView
+            android:id="@+id/tuiroomkit_iv_ai_live_subtitle_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="15dp"
+            android:layout_toEndOf="@+id/tuiroomkit_iv_ai_live_subtitle_ic"
+            android:clickable="false"
+            android:text="@string/tuiroomkit_ai_open_live_subtitle"
+            android:textColor="@color/tuiroomkit_color_text_light_grey"
+            android:textSize="14sp" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:layout_alignParentBottom="true"
+            android:background="@color/tuiroomkit_bg_dividing_line_grey" />
+
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/tuiroomkit_rl_ai_live_recording"
+        android:layout_width="0dp"
+        android:layout_height="56dp"
+        app:layout_constraintEnd_toEndOf="@+id/tuiroomkit_rl_ai_live_subtitle"
+        app:layout_constraintStart_toStartOf="@+id/tuiroomkit_rl_ai_live_subtitle"
+        app:layout_constraintTop_toBottomOf="@+id/tuiroomkit_rl_ai_live_subtitle">
+
+        <ImageView
+            android:id="@+id/tuiroomkit_iv_ai_live_recording"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_centerVertical="true"
+            android:clickable="false"
+            android:src="@drawable/tuiroomkit_ic_ai_live_record" />
+
+        <TextView
+            android:id="@+id/tuiroomkit_tv_ai_live_recording_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="15dp"
+            android:layout_toEndOf="@+id/tuiroomkit_iv_ai_live_recording"
+            android:clickable="false"
+            android:text="@string/tuiroomkit_ai_cat_live_record"
+            android:textColor="@color/tuiroomkit_color_text_light_grey"
+            android:textSize="14sp" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:visibility="gone"
+            android:layout_alignParentBottom="true"
+            android:background="@color/tuiroomkit_bg_dividing_line_grey" />
+    </RelativeLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 12 - 0
Android/tuiroomkit/src/main/res/layout-land/tuiroomkit_view_room_main.xml

@@ -108,6 +108,18 @@
         app:layout_constraintBottom_toBottomOf="@id/tuiroomkit_video_seat_container"
         app:layout_constraintStart_toStartOf="parent" />
 
+    <FrameLayout
+        android:id="@+id/tuiroomkit_speech_to_text_subtitle_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="72dp"
+        android:visibility="gone"
+        app:layout_constraintStart_toStartOf="@+id/tuiroomkit_video_seat_container"
+        app:layout_constraintEnd_toEndOf="@+id/tuiroomkit_video_seat_container"
+        app:layout_constraintBottom_toBottomOf="@id/tuiroomkit_video_seat_container"/>
+
     <FrameLayout
         android:id="@+id/tuiroomkit_bottom_view_container"
         android:layout_width="0dp"

+ 40 - 0
Android/tuiroomkit/src/main/res/layout/tuiroomkit_activity_speech_to_text.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/tuiroomkit_bg_main_color_black"
+    android:padding="12dp">
+
+    <Button
+        android:id="@+id/tuiroomkit_speech_to_text_back"
+        android:layout_width="16dp"
+        android:layout_height="16dp"
+        android:layout_marginTop="4dp"
+        android:background="@drawable/tuiroomkit_back_face_left"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/tuiroomkit_speech_to_text_title"
+        android:layout_width="0dp"
+        android:layout_height="24dp"
+        android:text="@string/tuiroomkit_ai_speech_to_text_record"
+        android:textColor="#D1D9EC"
+        android:textSize="16sp"
+        app:layout_constraintStart_toEndOf="@+id/tuiroomkit_speech_to_text_back"
+        app:layout_constraintTop_toTopOf="parent"
+        android:layout_marginStart="12dp"/>
+
+    <com.tencent.cloud.tuikit.roomkit.view.page.widget.SpeechToText.SpeechToTextRecyclerView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/tuiroomkit_speech_to_text_title"
+        android:layout_marginTop="12dp"
+        android:layout_marginBottom="12dp"
+        android:layout_marginStart="4dp"
+        android:layout_marginEnd="4dp"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+

+ 92 - 0
Android/tuiroomkit/src/main/res/layout/tuiroomkit_dialog_ai_assistant.xml

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/tuiroomkit_bg_bottom_dialog_black_portrait"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp"
+    android:paddingBottom="30dp">
+
+    <View
+        android:id="@+id/tuiroomkit_dialog_ai_hide"
+        android:layout_width="24dp"
+        android:layout_height="4dp"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="12dp"
+        android:background="@drawable/tuiroomkit_bottom_dialog_hide"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <RelativeLayout
+        android:id="@+id/tuiroomkit_rl_ai_live_subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="56dp"
+        app:layout_constraintTop_toBottomOf="@+id/tuiroomkit_dialog_ai_hide"
+        tools:layout_editor_absoluteX="16dp">
+
+        <ImageView
+            android:id="@+id/tuiroomkit_iv_ai_live_subtitle_ic"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_centerVertical="true"
+            android:clickable="false"
+            android:src="@drawable/tuiroomkit_ic_ai_live_subtitle" />
+
+        <TextView
+            android:id="@+id/tuiroomkit_iv_ai_live_subtitle_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="15dp"
+            android:layout_toEndOf="@+id/tuiroomkit_iv_ai_live_subtitle_ic"
+            android:clickable="false"
+            android:text="@string/tuiroomkit_ai_open_live_subtitle"
+            android:textColor="@color/tuiroomkit_color_text_light_grey"
+            android:textSize="14sp" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:layout_alignParentBottom="true"
+            android:background="@color/tuiroomkit_bg_dividing_line_grey" />
+
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:id="@+id/tuiroomkit_rl_ai_live_recording"
+        android:layout_width="match_parent"
+        android:layout_height="56dp"
+        app:layout_constraintTop_toBottomOf="@+id/tuiroomkit_rl_ai_live_subtitle">
+
+        <ImageView
+            android:id="@+id/tuiroomkit_iv_ai_live_recording"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_centerVertical="true"
+            android:clickable="false"
+            android:src="@drawable/tuiroomkit_ic_ai_live_record" />
+
+        <TextView
+            android:id="@+id/tuiroomkit_tv_ai_live_recording_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_marginStart="15dp"
+            android:layout_toEndOf="@+id/tuiroomkit_iv_ai_live_recording"
+            android:clickable="false"
+            android:text="@string/tuiroomkit_ai_cat_live_record"
+            android:textColor="@color/tuiroomkit_color_text_light_grey"
+            android:textSize="14sp" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:visibility="gone"
+            android:layout_alignParentBottom="true"
+            android:background="@color/tuiroomkit_bg_dividing_line_grey" />
+    </RelativeLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 0
Android/tuiroomkit/src/main/res/layout/tuiroomkit_float_chat_view_display.xml

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/tuiroomkit_cl_float_chat_display"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 

+ 29 - 0
Android/tuiroomkit/src/main/res/layout/tuiroomkit_item_speech_to_text_float_subtitle.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/tuiroomkit_ll_speech_to_text_float_subtitle"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="60dp"
+    android:background="@drawable/tuiroomkit_bg_speech_to_text_subtitle"
+    android:orientation="horizontal"
+    android:paddingTop="4dp"
+    android:paddingBottom="4dp">
+
+    <TextView
+        android:id="@+id/tuiroomkit_tv_speech_to_text_float_subtitle_user"
+        android:layout_width="wrap_content"
+        android:layout_height="20dp"
+        android:layout_marginStart="6dp"
+        android:textColor="#C5CCDB"
+        android:textSize="12sp" />
+
+    <TextView
+        android:id="@+id/tuiroomkit_tv_speech_to_text_float_subtitle_msg"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="10dp"
+        android:layout_marginEnd="10dp"
+        android:textColor="#F2F5FC"
+        android:textSize="12sp" />
+
+</LinearLayout>

+ 12 - 0
Android/tuiroomkit/src/main/res/layout/tuiroomkit_view_room_main.xml

@@ -108,6 +108,18 @@
         app:layout_constraintBottom_toBottomOf="@id/tuiroomkit_video_seat_container"
         app:layout_constraintStart_toStartOf="parent" />
 
+    <FrameLayout
+        android:id="@+id/tuiroomkit_speech_to_text_subtitle_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="64dp"
+        android:visibility="gone"
+        app:layout_constraintStart_toStartOf="@+id/tuiroomkit_video_seat_container"
+        app:layout_constraintEnd_toEndOf="@+id/tuiroomkit_video_seat_container"
+        app:layout_constraintBottom_toBottomOf="@id/tuiroomkit_video_seat_container" />
+
     <FrameLayout
         android:id="@+id/tuiroomkit_bottom_view_container"
         android:layout_width="0dp"

+ 26 - 0
Android/tuiroomkit/src/main/res/layout/tuiroomkit_view_speech_to_text_item.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="8dp"
+    android:layout_marginBottom="8dp"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/tuiroomkit_speech_to_text_item_title"
+        android:layout_width="match_parent"
+        android:layout_height="20dp"
+        android:textColor="#B2BBD1"
+        android:textSize="12sp"/>
+
+    <TextView
+        android:id="@+id/tuiroomkit_speech_to_text_item_content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@drawable/tuiroomkit_bg_speech_to_text_item_content"
+        android:padding="10dp"
+        android:textColor="#F5F5F5"
+        android:textSize="16sp"/>
+
+</LinearLayout>

+ 5 - 0
Android/tuiroomkit/src/main/res/values-ar/strings.xml

@@ -121,6 +121,11 @@
     <string name="tuiroomkit_manager_remove">إزالة المسؤول</string>
     <string name="tuiroomkit_mute_user">تعطيل الدردشة</string>
     <string name="tuiroomkit_kick_out_of_room">يزيل</string>
+    <string name="tuiroomkit_ai_tool">الذكاء الاصطناعي</string>
+    <string name="tuiroomkit_ai_speech_to_text_record">تسجيل الاجتماعات في الوقت الفعلي باستخدام الذكاء الاصطناعي</string>
+    <string name="tuiroomkit_ai_open_live_subtitle">فتح ترجمات الذكاء الاصطناعي في الوقت الفعلي</string>
+    <string name="tuiroomkit_ai_close_live_subtitle">إغلاق ترجمات الذكاء الاصطناعي في الوقت الفعلي</string>
+    <string name="tuiroomkit_ai_cat_live_record">عرض تسجيلات الاجتماعات المباشرة باستخدام الذكاء الاصطناعي</string>
     <string name="tuiroomkit_settings">الإعدادات</string>
     <string name="tuiroomkit_advanced_settings">إعدادات متقدمة</string>
     <string name="tuiroomkit_beauty">الجمال</string>

+ 5 - 0
Android/tuiroomkit/src/main/res/values-zh/strings.xml

@@ -121,6 +121,11 @@
     <string name="tuiroomkit_manager_remove">撤销管理员</string>
     <string name="tuiroomkit_mute_user">禁言</string>
     <string name="tuiroomkit_kick_out_of_room">踢出房间</string>
+    <string name="tuiroomkit_ai_tool">AI 助手</string>
+    <string name="tuiroomkit_ai_speech_to_text_record">AI 实时会议记录</string>
+    <string name="tuiroomkit_ai_open_live_subtitle">开启 AI 实时字幕</string>
+    <string name="tuiroomkit_ai_close_live_subtitle">关闭 AI 实时字幕</string>
+    <string name="tuiroomkit_ai_cat_live_record">查看 AI 实时会议记录</string>
     <string name="tuiroomkit_settings">设置</string>
     <string name="tuiroomkit_advanced_settings">高级设置</string>
     <string name="tuiroomkit_beauty">美颜</string>

+ 5 - 0
Android/tuiroomkit/src/main/res/values/strings.xml

@@ -121,6 +121,11 @@
     <string name="tuiroomkit_manager_remove">Remove administrator</string>
     <string name="tuiroomkit_mute_user">Disable chat</string>
     <string name="tuiroomkit_kick_out_of_room">Remove</string>
+    <string name="tuiroomkit_ai_tool">AI</string>
+    <string name="tuiroomkit_ai_speech_to_text_record">AI real-time meeting recording</string>
+    <string name="tuiroomkit_ai_open_live_subtitle">Open AI real-time subtitles</string>
+    <string name="tuiroomkit_ai_close_live_subtitle">Close AI real-time subtitles</string>
+    <string name="tuiroomkit_ai_cat_live_record">View AI Live Meeting Recording</string>
     <string name="tuiroomkit_settings">Settings</string>
     <string name="tuiroomkit_advanced_settings">AdvancedSettings</string>
     <string name="tuiroomkit_beauty">Beauty</string>