Sheffi пре 5 месеци
родитељ
комит
8b9a9e0b64
18 измењених фајлова са 882 додато и 270 уклоњено
  1. 35 1
      ybvideoandroid/live/src/main/java/com/yunbao/live/activity/LiveAnchorActivity.java
  2. 11 0
      ybvideoandroid/live/src/main/java/com/yunbao/live/activity/LiveAudienceActivity.java
  3. 17 1
      ybvideoandroid/live/src/main/java/com/yunbao/live/event/LinkMicTxMixStreamEvent.java
  4. 7 0
      ybvideoandroid/live/src/main/java/com/yunbao/live/interfaces/ILiveLinkMicViewHolder.java
  5. 261 64
      ybvideoandroid/live/src/main/java/com/yunbao/live/presenter/LiveLinkMicAnchorPresenter.java
  6. 2 2
      ybvideoandroid/live/src/main/java/com/yunbao/live/presenter/LiveLinkMicPresenter.java
  7. 116 95
      ybvideoandroid/live/src/main/java/com/yunbao/live/socket/SocketClient.java
  8. 40 3
      ybvideoandroid/live/src/main/java/com/yunbao/live/socket/SocketLinkMicAnchorUtil.java
  9. 6 0
      ybvideoandroid/live/src/main/java/com/yunbao/live/socket/SocketMessageListener.java
  10. 6 0
      ybvideoandroid/live/src/main/java/com/yunbao/live/views/AbsLivePushViewHolder.java
  11. 19 0
      ybvideoandroid/live/src/main/java/com/yunbao/live/views/LiveAnchorViewHolder.java
  12. 84 13
      ybvideoandroid/live/src/main/java/com/yunbao/live/views/LiveLinkMicPlayTxViewHolder.java
  13. 78 59
      ybvideoandroid/live/src/main/java/com/yunbao/live/views/LivePlayTxViewHolder.java
  14. 73 2
      ybvideoandroid/live/src/main/java/com/yunbao/live/views/LivePushTxViewHolder.java
  15. 26 0
      ybvideoandroid/live/src/main/res/drawable/selector_exit_link_mic_btn.xml
  16. 20 1
      ybvideoandroid/live/src/main/res/layout/view_live_anchor.xml
  17. 28 15
      ybvideoandroid/live/src/main/res/layout/view_live_play_tx.xml
  18. 53 14
      ybvideoandroid/live/src/main/res/layout/view_live_push_tx.xml

+ 35 - 1
ybvideoandroid/live/src/main/java/com/yunbao/live/activity/LiveAnchorActivity.java

@@ -65,6 +65,7 @@ import org.greenrobot.eventbus.ThreadMode;
 
 
 import java.io.File;
 import java.io.File;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
 
 
 import pl.droidsonroids.gif.GifImageView;
 import pl.droidsonroids.gif.GifImageView;
 
 
@@ -879,7 +880,7 @@ public class LiveAnchorActivity extends LiveActivity implements LiveFunctionClic
     @Subscribe(threadMode = ThreadMode.MAIN)
     @Subscribe(threadMode = ThreadMode.MAIN)
     public void onLinkMicTxMixStreamEvent(LinkMicTxMixStreamEvent e) {
     public void onLinkMicTxMixStreamEvent(LinkMicTxMixStreamEvent e) {
         if (mLivePushViewHolder != null && mLivePushViewHolder instanceof LivePushTxViewHolder) {
         if (mLivePushViewHolder != null && mLivePushViewHolder instanceof LivePushTxViewHolder) {
-            ((LivePushTxViewHolder) mLivePushViewHolder).onLinkMicTxMixStreamEvent(e.getType(), e.getToUid(), e.getToStream());
+            ((LivePushTxViewHolder) mLivePushViewHolder).onLinkMicTxMixStreamEvent(e.getType(), e.getToUid(), e.getToStream(), e.getMembers());
         }
         }
     }
     }
 
 
@@ -989,4 +990,37 @@ public class LiveAnchorActivity extends LiveActivity implements LiveFunctionClic
     public void setUpHotOrderId(String upHotOrderId) {
     public void setUpHotOrderId(String upHotOrderId) {
         mUpHotOrderId = upHotOrderId;
         mUpHotOrderId = upHotOrderId;
     }
     }
+
+    /**
+     * 主播连麦主播退出
+     */
+    @Override
+    public void onMultiLinkMicAnchorLeave(String uid) {
+        if (mLiveLinkMicAnchorPresenter != null) {
+            mLiveLinkMicAnchorPresenter.onMultiLinkMicAnchorLeave(uid);
+        }
+    }
+
+    /**
+     * 退出连麦
+     */
+    public void exitLinkMic() {
+        if (mLiveLinkMicAnchorPresenter != null && mLiveLinkMicAnchorPresenter.isLinkMic()) {
+            // 获取当前连麦的主播uid
+            String pkUid = mLiveLinkMicAnchorPresenter.getPkUid();
+            if (!TextUtils.isEmpty(pkUid)) {
+                // 主动断开连麦
+                mLiveLinkMicAnchorPresenter.closeLinkMic();
+            }
+        }
+    }
+
+    /**
+     * 更新退出连麦按钮显示状态
+     */
+    public void updateExitLinkMicBtnVisible(boolean visible) {
+        if (mLiveAnchorViewHolder != null) {
+            mLiveAnchorViewHolder.setExitLinkMicBtnVisible(visible);
+        }
+    }
 }
 }

+ 11 - 0
ybvideoandroid/live/src/main/java/com/yunbao/live/activity/LiveAudienceActivity.java

@@ -63,6 +63,7 @@ import org.greenrobot.eventbus.Subscribe;
 import org.greenrobot.eventbus.ThreadMode;
 import org.greenrobot.eventbus.ThreadMode;
 
 
 import java.util.List;
 import java.util.List;
+import java.util.Map;
 
 
 import pl.droidsonroids.gif.GifImageView;
 import pl.droidsonroids.gif.GifImageView;
 
 
@@ -540,6 +541,16 @@ public class LiveAudienceActivity extends LiveActivity {
         }
         }
     }
     }
 
 
+    /**
+     * 主播连麦主播退出
+     */
+    @Override
+    public void onMultiLinkMicAnchorLeave(String uid) {
+        if (mLiveLinkMicAnchorPresenter != null) {
+            mLiveLinkMicAnchorPresenter.onMultiLinkMicAnchorLeave(uid);
+        }
+    }
+
     @Override
     @Override
     public void onBackPressed() {
     public void onBackPressed() {
         if (!mEnd && !canBackPressed()) {
         if (!mEnd && !canBackPressed()) {

+ 17 - 1
ybvideoandroid/live/src/main/java/com/yunbao/live/event/LinkMicTxMixStreamEvent.java

@@ -1,5 +1,9 @@
 package com.yunbao.live.event;
 package com.yunbao.live.event;
 
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
 /**
 /**
  * Created by cxf on 2019/3/25.
  * Created by cxf on 2019/3/25.
  * 腾讯连麦的时候 主播混流
  * 腾讯连麦的时候 主播混流
@@ -11,12 +15,20 @@ public class LinkMicTxMixStreamEvent {
     private String mToUid;
     private String mToUid;
     private String mToStream;
     private String mToStream;
 
 
-    public LinkMicTxMixStreamEvent(int type, String touid, String toStream) {
+    private List<Map<String, String>> mMembers = new ArrayList<>();
+
+    public LinkMicTxMixStreamEvent(int type, String touid, String toStream, List<Map<String, String>> members) {
         mType = type;
         mType = type;
         mToUid = touid;
         mToUid = touid;
         mToStream = toStream;
         mToStream = toStream;
+        mMembers = members;
     }
     }
 
 
+//    public LinkMicTxMulitMixStreamEvent(int type, ArrayList<Map<String, String>> members) {
+//        mType = type;
+//        mMembers = members;
+//    }
+
     public int getType() {
     public int getType() {
         return mType;
         return mType;
     }
     }
@@ -28,4 +40,8 @@ public class LinkMicTxMixStreamEvent {
     public String getToStream() {
     public String getToStream() {
         return mToStream;
         return mToStream;
     }
     }
+
+    public List<Map<String, String>> getMembers() {
+        return mMembers;
+    }
 }
 }

+ 7 - 0
ybvideoandroid/live/src/main/java/com/yunbao/live/interfaces/ILiveLinkMicViewHolder.java

@@ -12,9 +12,16 @@ public interface ILiveLinkMicViewHolder {
 
 
     ViewGroup getRightContainer();
     ViewGroup getRightContainer();
 
 
+    ViewGroup getBottomRightContainer();
+
     ViewGroup getPkContainer();
     ViewGroup getPkContainer();
 
 
     void changeToLeft();
     void changeToLeft();
 
 
     void changeToBig();
     void changeToBig();
+
+    /**
+     * 切换到多人连麦布局
+     */
+    void changeToMultiLinkMic();
 }
 }

+ 261 - 64
ybvideoandroid/live/src/main/java/com/yunbao/live/presenter/LiveLinkMicAnchorPresenter.java

@@ -41,6 +41,13 @@ import com.yunbao.live.views.LiveLinkMicPlayTxViewHolder;
 
 
 import org.greenrobot.eventbus.EventBus;
 import org.greenrobot.eventbus.EventBus;
 
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 /**
 /**
  * Created by cxf on 2018/11/16.
  * Created by cxf on 2018/11/16.
  * 主播与主播连麦逻辑
  * 主播与主播连麦逻辑
@@ -50,37 +57,48 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
 
 
     private Context mContext;
     private Context mContext;
     private View mRoot;
     private View mRoot;
-    private boolean mIsAnchor;//自己是否是主播
+    private boolean mIsAnchor;// 自己是否是主播
     private SocketClient mSocketClient;
     private SocketClient mSocketClient;
     private ViewGroup mRightContainer;
     private ViewGroup mRightContainer;
-    private boolean mPlayingVideo;//是否播放对方主播的视频
-    private AbsLiveLinkMicPlayViewHolder mLiveLinkMicPlayViewHolder;//连麦播放小窗口
-    private String mPlayUrl;//自己直播间的播流地址
-    private boolean mIsApplyDialogShow;//是否显示了申请连麦的弹窗
-    private boolean mAcceptLinkMic;//是否接受连麦
-    private boolean mIsLinkMic;//是否已经连麦了
-    private String mApplyUid;//正在申请连麦的主播的uid
-    private String mApplyStream;//正在申请连麦的主播的stream
-    private String mPkUid;//正在连麦的对方主播的uid
+    private ViewGroup mBottomRightContainer;
+    private boolean mPlayingVideo;// 是否播放对方主播的视频
+    private AbsLiveLinkMicPlayViewHolder mLiveLinkMicPlayViewHolder;// 连麦播放小窗口
+    private AbsLiveLinkMicPlayViewHolder mThirdLiveLinkMicPlayViewHolder;// 连麦播放小窗口
+    private String mPlayUrl;// 自己直播间的播流地址
+    private boolean mIsApplyDialogShow;// 是否显示了申请连麦的弹窗
+    private boolean mAcceptLinkMic;// 是否接受连麦
+    private boolean mIsLinkMic;// 是否已经连麦了
+    private String mApplyUid;// 正在申请连麦的主播的uid
+    private String mApplyStream;// 正在申请连麦的主播的stream
+    private String mPkUid;// 正在连麦的对方主播的uid
     private TextView mLinkMicWaitText;
     private TextView mLinkMicWaitText;
-    private int mLinkMicWaitCount;//连麦弹窗等待倒计时
+    private int mLinkMicWaitCount;// 连麦弹窗等待倒计时
     private static final int LINK_MIC_COUNT_MAX = 10;
     private static final int LINK_MIC_COUNT_MAX = 10;
     private String mLinkMicWaitString;
     private String mLinkMicWaitString;
     private PopupWindow mLinkMicPopWindow;
     private PopupWindow mLinkMicPopWindow;
     private Handler mHandler;
     private Handler mHandler;
-    private boolean mPaused;//是否执行了Activity周期的pause
-    private long mLastApplyLinkMicTime;//上次申请连麦的时间
+    private boolean mPaused;// 是否执行了Activity周期的pause
+    private long mLastApplyLinkMicTime;// 上次申请连麦的时间
     private ILiveLinkMicViewHolder mLiveRoomPlayViewHolder;
     private ILiveLinkMicViewHolder mLiveRoomPlayViewHolder;
     private int mLiveSdk;
     private int mLiveSdk;
-    private String mSelfStream;//自己主播的stream
+    private String mSelfStream;// 自己主播的stream
+
+    // 新增多人连麦相关字段
+    private Map<String, LiveLinkMicPlayTxViewHolder> mLinkMicPlayViewHolderMap = new HashMap<>();
+    private List<Map<String, String>> mRoomMembers = new ArrayList<>(); // 房间成员列表
+    private List<Map<String, String>> mMembers = new ArrayList<Map<String, String>>();
+    private boolean mIsMultiLinkMic = false; // 是否为多人连麦模式
+    private static final int MAX_LINK_MIC_COUNT = 3; // 最大连麦人数
 
 
-    public LiveLinkMicAnchorPresenter(Context context, ILiveLinkMicViewHolder linkMicViewHolder, boolean isAnchor, int liveSdk, View root) {
+    public LiveLinkMicAnchorPresenter(Context context, ILiveLinkMicViewHolder linkMicViewHolder, boolean isAnchor,
+            int liveSdk, View root) {
         mContext = context;
         mContext = context;
         mIsAnchor = isAnchor;
         mIsAnchor = isAnchor;
         mLiveSdk = liveSdk;
         mLiveSdk = liveSdk;
         mRoot = root;
         mRoot = root;
         mLiveRoomPlayViewHolder = linkMicViewHolder;
         mLiveRoomPlayViewHolder = linkMicViewHolder;
         mRightContainer = linkMicViewHolder.getRightContainer();
         mRightContainer = linkMicViewHolder.getRightContainer();
+        mBottomRightContainer = linkMicViewHolder.getBottomRightContainer();
         mLinkMicWaitString = WordUtil.getString(R.string.link_mic_wait);
         mLinkMicWaitString = WordUtil.getString(R.string.link_mic_wait);
         mHandler = new Handler() {
         mHandler = new Handler() {
             @Override
             @Override
@@ -129,7 +147,7 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
     }
     }
 
 
     /**
     /**
-     * 主播与主播连麦  主播收到其他主播发过来的连麦申请的回调
+     * 主播与主播连麦 主播收到其他主播发过来的连麦申请的回调
      *
      *
      * @param u      对方主播的信息
      * @param u      对方主播的信息
      * @param stream 对方主播的stream
      * @param stream 对方主播的stream
@@ -165,8 +183,8 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
      * 显示申请连麦的弹窗
      * 显示申请连麦的弹窗
      */
      */
     private void showApplyDialog(UserBean u) {
     private void showApplyDialog(UserBean u) {
-        if(mIsAnchor){
-            ((LiveAnchorActivity)mContext).hideLinkMicAnchorWindow();
+        if (mIsAnchor) {
+            ((LiveAnchorActivity) mContext).hideLinkMicAnchorWindow();
         }
         }
         mIsApplyDialogShow = true;
         mIsApplyDialogShow = true;
         mAcceptLinkMic = false;
         mAcceptLinkMic = false;
@@ -174,17 +192,18 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
         ImageView avatar = (ImageView) v.findViewById(R.id.avatar);
         ImageView avatar = (ImageView) v.findViewById(R.id.avatar);
         TextView name = (TextView) v.findViewById(R.id.name);
         TextView name = (TextView) v.findViewById(R.id.name);
         ImageView sex = (ImageView) v.findViewById(R.id.sex);
         ImageView sex = (ImageView) v.findViewById(R.id.sex);
-//        ImageView level = (ImageView) v.findViewById(R.id.level);
+        // ImageView level = (ImageView) v.findViewById(R.id.level);
         mLinkMicWaitText = v.findViewById(R.id.wait_text);
         mLinkMicWaitText = v.findViewById(R.id.wait_text);
         v.findViewById(R.id.btn_refuse).setOnClickListener(this);
         v.findViewById(R.id.btn_refuse).setOnClickListener(this);
         v.findViewById(R.id.btn_accept).setOnClickListener(this);
         v.findViewById(R.id.btn_accept).setOnClickListener(this);
         ImgLoader.display(mContext, u.getAvatar(), avatar);
         ImgLoader.display(mContext, u.getAvatar(), avatar);
         name.setText(u.getUserNiceName());
         name.setText(u.getUserNiceName());
         sex.setImageResource(CommonIconUtil.getSexIcon(u.getSex()));
         sex.setImageResource(CommonIconUtil.getSexIcon(u.getSex()));
-//        LevelBean levelBean = CommonAppConfig.getInstance().getAnchorLevel(u.getLevelAnchor());
-//        if (levelBean != null) {
-//            ImgLoader.display(mContext, levelBean.getThumb(), level);
-//        }
+        // LevelBean levelBean =
+        // CommonAppConfig.getInstance().getAnchorLevel(u.getLevelAnchor());
+        // if (levelBean != null) {
+        // ImgLoader.display(mContext, levelBean.getThumb(), level);
+        // }
         mLinkMicWaitCount = LINK_MIC_COUNT_MAX;
         mLinkMicWaitCount = LINK_MIC_COUNT_MAX;
         mLinkMicWaitText.setText(mLinkMicWaitString + "(" + mLinkMicWaitCount + ")...");
         mLinkMicWaitText.setText(mLinkMicWaitString + "(" + mLinkMicWaitCount + ")...");
         mLinkMicPopWindow = new PopupWindow(v, DpUtil.dp2px(280), ViewGroup.LayoutParams.WRAP_CONTENT, true);
         mLinkMicPopWindow = new PopupWindow(v, DpUtil.dp2px(280), ViewGroup.LayoutParams.WRAP_CONTENT, true);
@@ -278,16 +297,17 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
      */
      */
     private void acceptLinkMic() {
     private void acceptLinkMic() {
         if (((LiveAnchorActivity) mContext).isBgmPlaying()) {
         if (((LiveAnchorActivity) mContext).isBgmPlaying()) {
-            DialogUitl.showSimpleDialog(mContext, WordUtil.getString(R.string.link_mic_close_bgm), new DialogUitl.SimpleCallback() {
-                @Override
-                public void onConfirmClick(Dialog dialog, String content) {
-                    ((LiveAnchorActivity) mContext).stopBgm();
-                    mAcceptLinkMic = true;
-                    if (mLinkMicPopWindow != null) {
-                        mLinkMicPopWindow.dismiss();
-                    }
-                }
-            });
+            DialogUitl.showSimpleDialog(mContext, WordUtil.getString(R.string.link_mic_close_bgm),
+                    new DialogUitl.SimpleCallback() {
+                        @Override
+                        public void onConfirmClick(Dialog dialog, String content) {
+                            ((LiveAnchorActivity) mContext).stopBgm();
+                            mAcceptLinkMic = true;
+                            if (mLinkMicPopWindow != null) {
+                                mLinkMicPopWindow.dismiss();
+                            }
+                        }
+                    });
         } else {
         } else {
             mAcceptLinkMic = true;
             mAcceptLinkMic = true;
             if (mLinkMicPopWindow != null) {
             if (mLinkMicPopWindow != null) {
@@ -299,57 +319,203 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
     /**
     /**
      * 主播自己主动断开连麦
      * 主播自己主动断开连麦
      */
      */
-    private void closeLinkMic() {
+    public void closeLinkMic() {
         SocketLinkMicAnchorUtil.linkMicAnchorClose(mSocketClient, mPkUid);
         SocketLinkMicAnchorUtil.linkMicAnchorClose(mSocketClient, mPkUid);
     }
     }
 
 
-
     /**
     /**
-     * 主播与主播连麦  所有人收到对方主播的播流地址的回调
+     * 主播与主播连麦 所有人收到对方主播的播流地址的回调
      *
      *
      * @param playUrl 对方主播的播流地址
      * @param playUrl 对方主播的播流地址
      */
      */
     public void onLinkMicAnchorPlayUrl(String pkUid, String playUrl) {
     public void onLinkMicAnchorPlayUrl(String pkUid, String playUrl) {
-        L.e("主播连麦----对方主播的播放地址---->" + playUrl);
+        L.e("主播连麦----对方主播的播放地址---->", playUrl);
         mApplyUid = null;
         mApplyUid = null;
         mLastApplyLinkMicTime = 0;
         mLastApplyLinkMicTime = 0;
         mIsLinkMic = true;
         mIsLinkMic = true;
-        mPkUid = pkUid;
+
+        // 检查mMember是否包含该pkUid
+        boolean hasUid = containsUid(pkUid);
+        if (!hasUid) {
+            Map<String, String> member = new HashMap<>();
+            member.put("uid", pkUid);
+            member.put("stream", extractStreamFromUrl(playUrl));
+            member.put("playUrl", playUrl);
+            mMembers.add(member);
+            L.e("主播连麦", "当前成员数量: " + mMembers.size());
+        }
+
+        // 检查是否已存在该主播的播放器
+        if (mLinkMicPlayViewHolderMap.containsKey(pkUid)) {
+            L.e("主播连麦", "播放器已存在,跳过创建: " + pkUid);
+            return;
+        }
+
         if (mIsAnchor || mLiveSdk != Constants.LIVE_SDK_TX) {
         if (mIsAnchor || mLiveSdk != Constants.LIVE_SDK_TX) {
-            if (mPlayingVideo) {
-                return;
+            // 创建新的播放器
+            LiveLinkMicPlayTxViewHolder playViewHolder;
+            L.e("主播连麦", "当前播放器数量: " + mLinkMicPlayViewHolderMap.size());
+            if (mLinkMicPlayViewHolderMap.size() == 0) {
+                playViewHolder = new LiveLinkMicPlayTxViewHolder(mContext, mRightContainer);
+                mLiveLinkMicPlayViewHolder = playViewHolder;
+            } else  {
+                playViewHolder = new LiveLinkMicPlayTxViewHolder(mContext, mBottomRightContainer);
+                mThirdLiveLinkMicPlayViewHolder = playViewHolder;
             }
             }
-            mPlayingVideo = true;
-            if (mLiveRoomPlayViewHolder != null) {
-                mLiveRoomPlayViewHolder.changeToLeft();
+            playViewHolder.setOnCloseListener(mIsAnchor ? this : null);
+            playViewHolder.addToParent();
+            playViewHolder.play(playUrl);
+            playViewHolder.showUserMessage(pkUid);
+
+            // 添加到Map中管理
+            mLinkMicPlayViewHolderMap.put(pkUid, playViewHolder);
+            L.e("主播连麦", "当前播放器数量: " + mLinkMicPlayViewHolderMap.size());
+
+            // 根据连麦人数调整布局
+            if (mLinkMicPlayViewHolderMap.size() == 1) {
+                // 第一个连麦者,使用两人连麦布局
+                if (mLiveRoomPlayViewHolder != null) {
+                    mLiveRoomPlayViewHolder.changeToLeft();
+                }
             }
             }
-            mLiveLinkMicPlayViewHolder = new LiveLinkMicPlayTxViewHolder(mContext, mRightContainer);
-            mLiveLinkMicPlayViewHolder.setOnCloseListener(mIsAnchor ? this : null);
-            mLiveLinkMicPlayViewHolder.addToParent();
-            mLiveLinkMicPlayViewHolder.play(playUrl);
-            mLiveLinkMicPlayViewHolder.showUserMessage(mPkUid);
 
 
             if (mIsAnchor) {
             if (mIsAnchor) {
                 ToastUtil.show(R.string.link_mic_anchor_accept_2);
                 ToastUtil.show(R.string.link_mic_anchor_accept_2);
                 ((LiveAnchorActivity) mContext).setPkBtnVisible(true);
                 ((LiveAnchorActivity) mContext).setPkBtnVisible(true);
+                // 显示退出连麦按钮
+                ((LiveAnchorActivity) mContext).updateExitLinkMicBtnVisible(true);
+
                 if (mLiveSdk == Constants.LIVE_SDK_TX) {
                 if (mLiveSdk == Constants.LIVE_SDK_TX) {
-                    //主播混流
-                    String toStream = null;
-                    int startIndex = playUrl.lastIndexOf("/");
-                    int endIndex = playUrl.indexOf("?", startIndex);
-                    if (startIndex >= 0 && startIndex < playUrl.length()
-                            && endIndex >= 0 && endIndex < playUrl.length()
-                            && startIndex < endIndex) {
-                        toStream = playUrl.substring(startIndex + 1, endIndex);
-                    }
+                    // 主播混流处理
+                    String toStream = extractStreamFromUrl(playUrl);
                     if (!TextUtils.isEmpty(toStream)) {
                     if (!TextUtils.isEmpty(toStream)) {
-                        EventBus.getDefault().post(new LinkMicTxMixStreamEvent(Constants.LINK_MIC_TYPE_ANCHOR,pkUid ,toStream));
+                        EventBus.getDefault()
+                                .post(new LinkMicTxMixStreamEvent(Constants.LINK_MIC_TYPE_ANCHOR, pkUid, toStream, mMembers));
                     }
                     }
                 }
                 }
             }
             }
         }
         }
     }
     }
 
 
+    public boolean containsUid(String uid) {
+        for (Map<String, String> member : mMembers) {
+            if (uid.equals(member.get("uid"))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 从播放URL中提取stream
+     */
+    private String extractStreamFromUrl(String playUrl) {
+        String toStream = null;
+        int startIndex = playUrl.lastIndexOf("/");
+        int endIndex = playUrl.indexOf("?", startIndex);
+        if (startIndex >= 0 && startIndex < playUrl.length()
+                && endIndex >= 0 && endIndex < playUrl.length()
+                && startIndex < endIndex) {
+            toStream = playUrl.substring(startIndex + 1, endIndex);
+        }
+        return toStream;
+    }
+
+ public void onMultiLinkMicAnchorLeave(String uid) {
+        removeLinkMicPlayer(uid);
+ }
+
+    /**
+     * 移除指定主播的播放器
+     */
+    /**
+     * 移除指定主播的播放器
+     */
+    private void removeLinkMicPlayer(String uid) {
+        LiveLinkMicPlayTxViewHolder viewHolder = mLinkMicPlayViewHolderMap.get(uid);
+        L.e("主播连麦", "所有的播放器" + mLinkMicPlayViewHolderMap.toString());
+        if (viewHolder == null) {
+            L.e("主播连麦", "要移除的播放器不存在: " + uid);
+            return;
+        }
+
+        // 从成员列表中移除
+        Iterator<Map<String, String>> iterator = mMembers.iterator();
+        while (iterator.hasNext()) {
+            Map<String, String> member = iterator.next();
+            if (member.get("uid").equals(uid)) {
+                iterator.remove();
+                break;
+            }
+        }
+
+        // 释放并移除视图
+        viewHolder.release();
+        viewHolder.removeFromParent();
+
+        // 从Map中移除
+        mLinkMicPlayViewHolderMap.remove(uid);
+
+        // 更新视图引用
+        if (viewHolder == mLiveLinkMicPlayViewHolder) {
+            mLiveLinkMicPlayViewHolder = null;
+            // 如果还有其他连麦者,将第三个播放器提升为主播放器
+            if (mThirdLiveLinkMicPlayViewHolder != null && mMembers.size() > 0) {
+                // 找到对应的uid
+                String remainingUid = null;
+                for (Map.Entry<String, LiveLinkMicPlayTxViewHolder> entry : mLinkMicPlayViewHolderMap.entrySet()) {
+                    if (entry.getValue() == mThirdLiveLinkMicPlayViewHolder) {
+                        remainingUid = entry.getKey();
+                        break;
+                    }
+                }
+
+                if (remainingUid != null) {
+                    // 移除第三个播放器
+                    mThirdLiveLinkMicPlayViewHolder.removeFromParent();
+
+                    // 创建新的主播放器
+                    Map<String, String> remainingMember = null;
+                    for (Map<String, String> member : mMembers) {
+                        if (member.get("uid").equals(remainingUid)) {
+                            remainingMember = member;
+                            break;
+                        }
+                    }
+
+                    if (remainingMember != null) {
+                        LiveLinkMicPlayTxViewHolder newMainPlayer = new LiveLinkMicPlayTxViewHolder(mContext, mRightContainer);
+                        newMainPlayer.setOnCloseListener(mIsAnchor ? this : null);
+                        newMainPlayer.addToParent();
+                        newMainPlayer.play(remainingMember.get("playUrl"));
+                        newMainPlayer.showUserMessage(remainingUid);
+
+                        mLiveLinkMicPlayViewHolder = newMainPlayer;
+                        mLinkMicPlayViewHolderMap.put(remainingUid, newMainPlayer);
+                    }
+
+                    mThirdLiveLinkMicPlayViewHolder = null;
+                }
+            }
+        } else if (viewHolder == mThirdLiveLinkMicPlayViewHolder) {
+            mThirdLiveLinkMicPlayViewHolder = null;
+        }
+
+        // 如果没有连麦者了,关闭连麦
+        if (mLinkMicPlayViewHolderMap.size() == 0) {
+            onLinkMicAnchorClose();
+            return;
+        }
+
+        // 更新混流
+        if (mIsAnchor && mLiveSdk == Constants.LIVE_SDK_TX) {
+            EventBus.getDefault()
+                    .post(new LinkMicTxMixStreamEvent(Constants.LINK_MIC_TYPE_ANCHOR, null, null, mMembers));
+        }
+
+        L.e("主播连麦", "移除播放器完成,剩余播放器数量: " + mLinkMicPlayViewHolderMap.size());
+    }
+
     /**
     /**
      * 主播与主播连麦 断开连麦的回调
      * 主播与主播连麦 断开连麦的回调
      */
      */
@@ -357,6 +523,16 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
         if (mIsLinkMic) {
         if (mIsLinkMic) {
             ToastUtil.show(R.string.link_mic_anchor_close);
             ToastUtil.show(R.string.link_mic_anchor_close);
         }
         }
+        // 清理所有连麦播放器
+        for (LiveLinkMicPlayTxViewHolder viewHolder : mLinkMicPlayViewHolderMap.values()) {
+            if (viewHolder != null) {
+                viewHolder.release();
+                viewHolder.removeFromParent();
+            }
+        }
+        mLinkMicPlayViewHolderMap.clear();
+        mRoomMembers.clear();
+        mIsMultiLinkMic = false;
         if (mHandler != null) {
         if (mHandler != null) {
             mHandler.removeCallbacksAndMessages(null);
             mHandler.removeCallbacksAndMessages(null);
         }
         }
@@ -381,8 +557,10 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
         mPkUid = null;
         mPkUid = null;
         if (mIsAnchor) {
         if (mIsAnchor) {
             ((LiveAnchorActivity) mContext).setPkBtnVisible(false);
             ((LiveAnchorActivity) mContext).setPkBtnVisible(false);
+            // 隐藏退出连麦按钮
+            ((LiveAnchorActivity) mContext).updateExitLinkMicBtnVisible(false);
             if (mLiveSdk == Constants.LIVE_SDK_TX) {
             if (mLiveSdk == Constants.LIVE_SDK_TX) {
-                EventBus.getDefault().post(new LinkMicTxMixStreamEvent(Constants.LINK_MIC_TYPE_ANCHOR, null,null));
+                EventBus.getDefault().post(new LinkMicTxMixStreamEvent(Constants.LINK_MIC_TYPE_ANCHOR, null, null, null));
             }
             }
         }
         }
     }
     }
@@ -396,7 +574,7 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
     }
     }
 
 
     /**
     /**
-     * 主播与主播连麦  对方主播无响应的回调
+     * 主播与主播连麦 对方主播无响应的回调
      */
      */
     public void onLinkMicNotResponse() {
     public void onLinkMicNotResponse() {
         mLastApplyLinkMicTime = 0;
         mLastApplyLinkMicTime = 0;
@@ -404,7 +582,7 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
     }
     }
 
 
     /**
     /**
-     * 主播与主播连麦  对方主播正在忙的回调
+     * 主播与主播连麦 对方主播正在忙的回调
      */
      */
     public void onLinkMicAnchorBusy() {
     public void onLinkMicAnchorBusy() {
         mLastApplyLinkMicTime = 0;
         mLastApplyLinkMicTime = 0;
@@ -416,6 +594,9 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
         if (mLiveLinkMicPlayViewHolder != null) {
         if (mLiveLinkMicPlayViewHolder != null) {
             mLiveLinkMicPlayViewHolder.pause();
             mLiveLinkMicPlayViewHolder.pause();
         }
         }
+        if (mThirdLiveLinkMicPlayViewHolder != null) {
+            mThirdLiveLinkMicPlayViewHolder.pause();
+        }
     }
     }
 
 
     public void resume() {
     public void resume() {
@@ -424,6 +605,9 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
             if (mLiveLinkMicPlayViewHolder != null) {
             if (mLiveLinkMicPlayViewHolder != null) {
                 mLiveLinkMicPlayViewHolder.resume();
                 mLiveLinkMicPlayViewHolder.resume();
             }
             }
+            if (mThirdLiveLinkMicPlayViewHolder != null) {
+                mThirdLiveLinkMicPlayViewHolder.resume();
+            }
         }
         }
     }
     }
 
 
@@ -437,6 +621,10 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
             mLiveLinkMicPlayViewHolder.release();
             mLiveLinkMicPlayViewHolder.release();
         }
         }
         mLiveLinkMicPlayViewHolder = null;
         mLiveLinkMicPlayViewHolder = null;
+        if (mThirdLiveLinkMicPlayViewHolder != null) {
+            mThirdLiveLinkMicPlayViewHolder.release();
+        }
+        mThirdLiveLinkMicPlayViewHolder = null;
     }
     }
 
 
     /**
     /**
@@ -447,8 +635,12 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
             ToastUtil.show(R.string.live_game_cannot_link_mic);
             ToastUtil.show(R.string.live_game_cannot_link_mic);
             return false;
             return false;
         }
         }
-        if (mIsLinkMic || ((LiveActivity) mContext).isLinkMic()) {
-            ToastUtil.show(mIsAnchor ? R.string.live_link_mic_cannot_link_2 : R.string.live_link_mic_cannot_link);
+        // if (mIsLinkMic || ((LiveActivity) mContext).isLinkMic()) {
+        //     ToastUtil.show(mIsAnchor ? R.string.live_link_mic_cannot_link_2 : R.string.live_link_mic_cannot_link);
+        //     return false;
+        // }
+        if (mLinkMicPlayViewHolderMap.size() >= MAX_LINK_MIC_COUNT - 1) {
+            ToastUtil.show("连麦人数已达上限");
             return false;
             return false;
         }
         }
         if (System.currentTimeMillis() - mLastApplyLinkMicTime < 11000) {
         if (System.currentTimeMillis() - mLastApplyLinkMicTime < 11000) {
@@ -481,6 +673,11 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
             mLiveLinkMicPlayViewHolder.removeFromParent();
             mLiveLinkMicPlayViewHolder.removeFromParent();
         }
         }
         mLiveLinkMicPlayViewHolder = null;
         mLiveLinkMicPlayViewHolder = null;
+        if (mThirdLiveLinkMicPlayViewHolder != null) {
+            mThirdLiveLinkMicPlayViewHolder.release();
+            mThirdLiveLinkMicPlayViewHolder.removeFromParent();
+        }
+        mThirdLiveLinkMicPlayViewHolder = null;
         if (mLiveRoomPlayViewHolder != null) {
         if (mLiveRoomPlayViewHolder != null) {
             mLiveRoomPlayViewHolder.changeToBig();
             mLiveRoomPlayViewHolder.changeToBig();
         }
         }
@@ -491,7 +688,7 @@ public class LiveLinkMicAnchorPresenter implements View.OnClickListener {
     }
     }
 
 
     /**
     /**
-     * 主播与主播连麦  对方主播正在游戏
+     * 主播与主播连麦 对方主播正在游戏
      */
      */
     public void onlinkMicPlayGaming() {
     public void onlinkMicPlayGaming() {
         mLastApplyLinkMicTime = 0;
         mLastApplyLinkMicTime = 0;

+ 2 - 2
ybvideoandroid/live/src/main/java/com/yunbao/live/presenter/LiveLinkMicPresenter.java

@@ -246,7 +246,7 @@ public class LiveLinkMicPresenter implements View.OnClickListener {
                         && startIndex < endIndex) {
                         && startIndex < endIndex) {
                     String toStream = playUrl.substring(startIndex + 1, endIndex);
                     String toStream = playUrl.substring(startIndex + 1, endIndex);
                     if (!TextUtils.isEmpty(toStream)) {
                     if (!TextUtils.isEmpty(toStream)) {
-                        EventBus.getDefault().post(new LinkMicTxMixStreamEvent(Constants.LINK_MIC_TYPE_NORMAL,uid ,toStream));
+                        EventBus.getDefault().post(new LinkMicTxMixStreamEvent(Constants.LINK_MIC_TYPE_NORMAL,uid ,toStream, null));
                     }
                     }
                 }
                 }
             }
             }
@@ -289,7 +289,7 @@ public class LiveLinkMicPresenter implements View.OnClickListener {
                 }
                 }
                 mLiveLinkMicPlayViewHolder = null;
                 mLiveLinkMicPlayViewHolder = null;
                 if (mIsAnchor && mLiveSdk == Constants.LIVE_SDK_TX) {
                 if (mIsAnchor && mLiveSdk == Constants.LIVE_SDK_TX) {
-                    EventBus.getDefault().post(new LinkMicTxMixStreamEvent(Constants.LINK_MIC_TYPE_NORMAL, null,null));
+                    EventBus.getDefault().post(new LinkMicTxMixStreamEvent(Constants.LINK_MIC_TYPE_NORMAL, null,null, null));
                 }
                 }
             }
             }
             mIsLinkMic = false;
             mIsLinkMic = false;

+ 116 - 95
ybvideoandroid/live/src/main/java/com/yunbao/live/socket/SocketClient.java

@@ -1,6 +1,5 @@
 package com.yunbao.live.socket;
 package com.yunbao.live.socket;
 
 
-
 import android.graphics.PointF;
 import android.graphics.PointF;
 import android.os.Handler;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Message;
@@ -32,6 +31,7 @@ import org.json.JSONException;
 
 
 import java.lang.ref.WeakReference;
 import java.lang.ref.WeakReference;
 import java.util.List;
 import java.util.List;
+import java.util.Map;
 
 
 import io.socket.client.IO;
 import io.socket.client.IO;
 import io.socket.client.Socket;
 import io.socket.client.Socket;
@@ -57,13 +57,13 @@ public class SocketClient {
                 option.reconnection = true;
                 option.reconnection = true;
                 option.reconnectionDelay = 2000;
                 option.reconnectionDelay = 2000;
                 mSocket = IO.socket(url, option);
                 mSocket = IO.socket(url, option);
-                mSocket.on(Socket.EVENT_CONNECT, mConnectListener);//连接成功
-                mSocket.on(Socket.EVENT_DISCONNECT, mDisConnectListener);//断开连接
-                mSocket.on(Socket.EVENT_CONNECT_ERROR, mErrorListener);//连接错误
-                mSocket.on(Socket.EVENT_CONNECT_TIMEOUT, mTimeOutListener);//连接超时
-                mSocket.on(Socket.EVENT_RECONNECT, mReConnectListener);//重连
-                mSocket.on(Constants.SOCKET_CONN, onConn);//连接socket消息
-                mSocket.on(Constants.SOCKET_BROADCAST, onBroadcast);//接收服务器广播的具体业务逻辑相关的消息
+                mSocket.on(Socket.EVENT_CONNECT, mConnectListener);// 连接成功
+                mSocket.on(Socket.EVENT_DISCONNECT, mDisConnectListener);// 断开连接
+                mSocket.on(Socket.EVENT_CONNECT_ERROR, mErrorListener);// 连接错误
+                mSocket.on(Socket.EVENT_CONNECT_TIMEOUT, mTimeOutListener);// 连接超时
+                mSocket.on(Socket.EVENT_RECONNECT, mReConnectListener);// 重连
+                mSocket.on(Constants.SOCKET_CONN, onConn);// 连接socket消息
+                mSocket.on(Constants.SOCKET_BROADCAST, onBroadcast);// 接收服务器广播的具体业务逻辑相关的消息
                 mSocketHandler = new SocketHandler(listener);
                 mSocketHandler = new SocketHandler(listener);
             } catch (Exception e) {
             } catch (Exception e) {
                 L.e(TAG, "socket url 异常--->" + e.getMessage());
                 L.e(TAG, "socket url 异常--->" + e.getMessage());
@@ -71,7 +71,6 @@ public class SocketClient {
         }
         }
     }
     }
 
 
-
     public void connect(String liveuid, String stream) {
     public void connect(String liveuid, String stream) {
         mLiveUid = liveuid;
         mLiveUid = liveuid;
         mStream = stream;
         mStream = stream;
@@ -113,7 +112,6 @@ public class SocketClient {
         }
         }
     }
     }
 
 
-
     private Emitter.Listener mConnectListener = new Emitter.Listener() {
     private Emitter.Listener mConnectListener = new Emitter.Listener() {
         @Override
         @Override
         public void call(Object... args) {
         public void call(Object... args) {
@@ -126,7 +124,7 @@ public class SocketClient {
         @Override
         @Override
         public void call(Object... args) {
         public void call(Object... args) {
             L.e(TAG, "--reConnect-->" + args);
             L.e(TAG, "--reConnect-->" + args);
-            //conn();
+            // conn();
         }
         }
     };
     };
 
 
@@ -193,7 +191,6 @@ public class SocketClient {
         }
         }
     };
     };
 
 
-
     public void send(SocketSendBean bean) {
     public void send(SocketSendBean bean) {
         if (mSocket != null) {
         if (mSocket != null) {
             mSocket.emit(Constants.SOCKET_SEND, bean.create());
             mSocket.emit(Constants.SOCKET_SEND, bean.create());
@@ -227,7 +224,6 @@ public class SocketClient {
             return ct;
             return ct;
         }
         }
 
 
-
         @Override
         @Override
         public void handleMessage(Message msg) {
         public void handleMessage(Message msg) {
             if (mListener == null) {
             if (mListener == null) {
@@ -246,37 +242,36 @@ public class SocketClient {
             }
             }
         }
         }
 
 
-
         private void processBroadcast(String socketMsg) {
         private void processBroadcast(String socketMsg) {
             L.e("收到socket--->" + socketMsg);
             L.e("收到socket--->" + socketMsg);
-//            if (Constants.SOCKET_STOP_PLAY.equals(socketMsg)) {
-//                mListener.onSuperCloseLive();//超管关闭房间
-//                return;
-//            }
+            // if (Constants.SOCKET_STOP_PLAY.equals(socketMsg)) {
+            // mListener.onSuperCloseLive();//超管关闭房间
+            // return;
+            // }
             SocketReceiveBean received = JSON.parseObject(socketMsg, SocketReceiveBean.class);
             SocketReceiveBean received = JSON.parseObject(socketMsg, SocketReceiveBean.class);
             JSONObject map = received.getMsg().getJSONObject(0);
             JSONObject map = received.getMsg().getJSONObject(0);
             switch (map.getString("_method_")) {
             switch (map.getString("_method_")) {
-                case Constants.SOCKET_STOP_LIVE://超管封闭直播间
+                case Constants.SOCKET_STOP_LIVE:// 超管封闭直播间
                     mListener.onSuperCloseLive(map.getString("ct"));
                     mListener.onSuperCloseLive(map.getString("ct"));
                     break;
                     break;
-                case Constants.SOCKET_SYSTEM_WARNING://后台警告
+                case Constants.SOCKET_SYSTEM_WARNING:// 后台警告
                     mListener.onSuperWarningLive(getLanguageCt(map));
                     mListener.onSuperWarningLive(getLanguageCt(map));
                     break;
                     break;
-                case Constants.SOCKET_SYSTEM://系统消息
+                case Constants.SOCKET_SYSTEM:// 系统消息
                     systemChatMessage(getLanguageCt(map));
                     systemChatMessage(getLanguageCt(map));
                     break;
                     break;
-                case Constants.SOCKET_KICK://踢人
+                case Constants.SOCKET_KICK:// 踢人
                     systemChatMessage(getLanguageCt(map));
                     systemChatMessage(getLanguageCt(map));
                     mListener.onKick(map.getString("touid"));
                     mListener.onKick(map.getString("touid"));
                     break;
                     break;
-                case Constants.SOCKET_SHUT_UP://禁言
+                case Constants.SOCKET_SHUT_UP:// 禁言
                     String ct = getLanguageCt(map);
                     String ct = getLanguageCt(map);
                     systemChatMessage(ct);
                     systemChatMessage(ct);
                     mListener.onShutUp(map.getString("touid"), ct);
                     mListener.onShutUp(map.getString("touid"), ct);
                     break;
                     break;
-                case Constants.SOCKET_SEND_MSG://文字消息,点亮,用户进房间
+                case Constants.SOCKET_SEND_MSG:// 文字消息,点亮,用户进房间
                     String msgtype = map.getString("msgtype");
                     String msgtype = map.getString("msgtype");
-                    if ("2".equals(msgtype)) {//发言,点亮
+                    if ("2".equals(msgtype)) {// 发言,点亮
                         if ("409002".equals(received.getRetcode())) {
                         if ("409002".equals(received.getRetcode())) {
                             ToastUtil.show(R.string.live_you_are_shut);
                             ToastUtil.show(R.string.live_you_are_shut);
                             return;
                             return;
@@ -293,50 +288,57 @@ public class SocketClient {
                         if (heart > 0) {
                         if (heart > 0) {
                             chatBean.setType(LiveChatBean.LIGHT);
                             chatBean.setType(LiveChatBean.LIGHT);
                         }
                         }
-                        /*chatBean.setLiangName(map.getString("liangname"));
-                        chatBean.setVipType(map.getIntValue("vip_type"));*/
+                        /*
+                         * chatBean.setLiangName(map.getString("liangname"));
+                         * chatBean.setVipType(map.getIntValue("vip_type"));
+                         */
                         chatBean.setGuardType(map.getIntValue("guard_type"));
                         chatBean.setGuardType(map.getIntValue("guard_type"));
                         mListener.onChat(chatBean);
                         mListener.onChat(chatBean);
-                    } else if ("0".equals(msgtype)) {//用户进入房间
+                    } else if ("0".equals(msgtype)) {// 用户进入房间
                         JSONObject obj = JSON.parseObject(map.getString("ct"));
                         JSONObject obj = JSON.parseObject(map.getString("ct"));
                         LiveUserGiftBean u = JSON.toJavaObject(obj, LiveUserGiftBean.class);
                         LiveUserGiftBean u = JSON.toJavaObject(obj, LiveUserGiftBean.class);
-                        /*UserBean.Vip vip = new UserBean.Vip();
-                        int isvip = obj.getIntValue("isvip");
-                        vip.setIsvip(isvip);
-                        u.setVipInfo(vip);
-                        UserBean.Car car = new UserBean.Car();
-                        car.setId(obj.getIntValue("car_id"));
-                        car.setSwf(obj.getString("car_swf"));
-                        car.setSwftime(obj.getFloatValue("car_swftime"));
-                        car.setWords(obj.getString("car_words"));
-                        u.setCar(car);
-                        UserBean.Liang liang = new UserBean.Liang();
-                        String liangName = obj.getString("liangname");
-                        liang.setName(liangName);
-                        u.setLiang(liang);*/
+                        /*
+                         * UserBean.Vip vip = new UserBean.Vip();
+                         * int isvip = obj.getIntValue("isvip");
+                         * vip.setIsvip(isvip);
+                         * u.setVipInfo(vip);
+                         * UserBean.Car car = new UserBean.Car();
+                         * car.setId(obj.getIntValue("car_id"));
+                         * car.setSwf(obj.getString("car_swf"));
+                         * car.setSwftime(obj.getFloatValue("car_swftime"));
+                         * car.setWords(obj.getString("car_words"));
+                         * u.setCar(car);
+                         * UserBean.Liang liang = new UserBean.Liang();
+                         * String liangName = obj.getString("liangname");
+                         * liang.setName(liangName);
+                         * u.setLiang(liang);
+                         */
                         LiveChatBean chatBean = new LiveChatBean();
                         LiveChatBean chatBean = new LiveChatBean();
                         chatBean.setType(LiveChatBean.ENTER_ROOM);
                         chatBean.setType(LiveChatBean.ENTER_ROOM);
                         chatBean.setId(u.getId());
                         chatBean.setId(u.getId());
                         chatBean.setUserNiceName(u.getUserNiceName());
                         chatBean.setUserNiceName(u.getUserNiceName());
-                        //chatBean.setLevel(u.getLevel());
-                        /*chatBean.setVipType(isvip);
-                        chatBean.setLiangName(liangName);*/
+                        // chatBean.setLevel(u.getLevel());
+                        /*
+                         * chatBean.setVipType(isvip);
+                         * chatBean.setLiangName(liangName);
+                         */
                         chatBean.setManager(obj.getIntValue("usertype") == Constants.SOCKET_USER_TYPE_ADMIN);
                         chatBean.setManager(obj.getIntValue("usertype") == Constants.SOCKET_USER_TYPE_ADMIN);
                         chatBean.setContent(WordUtil.getString(R.string.live_enter_room));
                         chatBean.setContent(WordUtil.getString(R.string.live_enter_room));
                         chatBean.setGuardType(obj.getIntValue("guard_type"));
                         chatBean.setGuardType(obj.getIntValue("guard_type"));
                         mListener.onEnterRoom(new LiveEnterRoomBean(u, chatBean));
                         mListener.onEnterRoom(new LiveEnterRoomBean(u, chatBean));
                     }
                     }
                     break;
                     break;
-                case Constants.SOCKET_LIGHT://飘心
+                case Constants.SOCKET_LIGHT:// 飘心
                     mListener.onLight();
                     mListener.onLight();
                     break;
                     break;
-                case Constants.SOCKET_SEND_GIFT://送礼物
-                    LiveReceiveGiftBean receiveGiftBean = JSON.parseObject(map.getString("ct"), LiveReceiveGiftBean.class);
+                case Constants.SOCKET_SEND_GIFT:// 送礼物
+                    LiveReceiveGiftBean receiveGiftBean = JSON.parseObject(map.getString("ct"),
+                            LiveReceiveGiftBean.class);
                     receiveGiftBean.setAvatar(map.getString("uhead"));
                     receiveGiftBean.setAvatar(map.getString("uhead"));
                     receiveGiftBean.setUserNiceName(map.getString("uname"));
                     receiveGiftBean.setUserNiceName(map.getString("uname"));
 
 
-                    if(receiveGiftBean.getType()==2){
-                        List<PointF> list=JSON.parseArray(map.getString("paintedPath"),PointF.class);
+                    if (receiveGiftBean.getType() == 2) {
+                        List<PointF> list = JSON.parseArray(map.getString("paintedPath"), PointF.class);
                         receiveGiftBean.setPointList(list);
                         receiveGiftBean.setPointList(list);
                         receiveGiftBean.setDrawWidth(map.getFloat("paintedWidth"));
                         receiveGiftBean.setDrawWidth(map.getFloat("paintedWidth"));
                         receiveGiftBean.setDrawHeight(map.getFloat("paintedHeight"));
                         receiveGiftBean.setDrawHeight(map.getFloat("paintedHeight"));
@@ -355,17 +357,17 @@ public class SocketClient {
                         mListener.onSendGift(receiveGiftBean);
                         mListener.onSendGift(receiveGiftBean);
                     }
                     }
                     break;
                     break;
-                case Constants.SOCKET_SEND_BARRAGE://发弹幕
+                case Constants.SOCKET_SEND_BARRAGE:// 发弹幕
                     LiveDanMuBean liveDanMuBean = JSON.parseObject(map.getString("ct"), LiveDanMuBean.class);
                     LiveDanMuBean liveDanMuBean = JSON.parseObject(map.getString("ct"), LiveDanMuBean.class);
                     liveDanMuBean.setAvatar(map.getString("uhead"));
                     liveDanMuBean.setAvatar(map.getString("uhead"));
                     liveDanMuBean.setUserNiceName(map.getString("uname"));
                     liveDanMuBean.setUserNiceName(map.getString("uname"));
                     mListener.onSendDanMu(liveDanMuBean);
                     mListener.onSendDanMu(liveDanMuBean);
                     break;
                     break;
-                case Constants.SOCKET_LEAVE_ROOM://离开房间
+                case Constants.SOCKET_LEAVE_ROOM:// 离开房间
                     UserBean u = JSON.parseObject(map.getString("ct"), UserBean.class);
                     UserBean u = JSON.parseObject(map.getString("ct"), UserBean.class);
                     mListener.onLeaveRoom(u);
                     mListener.onLeaveRoom(u);
                     break;
                     break;
-                case Constants.SOCKET_LIVE_END://主播关闭直播
+                case Constants.SOCKET_LIVE_END:// 主播关闭直播
                     int action = map.getIntValue("action");
                     int action = map.getIntValue("action");
                     if (action == 18) {
                     if (action == 18) {
                         mListener.onLiveEnd();
                         mListener.onLiveEnd();
@@ -373,7 +375,7 @@ public class SocketClient {
                         mListener.onAnchorInvalid();
                         mListener.onAnchorInvalid();
                     }
                     }
                     break;
                     break;
-                case Constants.SOCKET_CHANGE_LIVE://主播切换计时收费类型
+                case Constants.SOCKET_CHANGE_LIVE:// 主播切换计时收费类型
                     mListener.onChangeTimeCharge(map.getIntValue("type_val"));
                     mListener.onChangeTimeCharge(map.getIntValue("type_val"));
                     break;
                     break;
                 case Constants.SOCKET_UPDATE_VOTES:
                 case Constants.SOCKET_UPDATE_VOTES:
@@ -386,11 +388,11 @@ public class SocketClient {
                     List<LiveUserGiftBean> list = JSON.parseArray(s, LiveUserGiftBean.class);
                     List<LiveUserGiftBean> list = JSON.parseArray(s, LiveUserGiftBean.class);
                     mListener.addFakeFans(list);
                     mListener.addFakeFans(list);
                     break;
                     break;
-                case Constants.SOCKET_SET_ADMIN://设置或取消管理员
+                case Constants.SOCKET_SET_ADMIN:// 设置或取消管理员
                     systemChatMessage(getLanguageCt(map));
                     systemChatMessage(getLanguageCt(map));
                     mListener.onSetAdmin(map.getString("touid"), map.getIntValue("action"));
                     mListener.onSetAdmin(map.getString("touid"), map.getIntValue("action"));
                     break;
                     break;
-                case Constants.SOCKET_BUY_GUARD://购买守护
+                case Constants.SOCKET_BUY_GUARD:// 购买守护
                     LiveBuyGuardMsgBean buyGuardMsgBean = new LiveBuyGuardMsgBean();
                     LiveBuyGuardMsgBean buyGuardMsgBean = new LiveBuyGuardMsgBean();
                     buyGuardMsgBean.setUid(map.getString("uid"));
                     buyGuardMsgBean.setUid(map.getString("uid"));
                     buyGuardMsgBean.setUserName(map.getString("uname"));
                     buyGuardMsgBean.setUserName(map.getString("uname"));
@@ -399,16 +401,16 @@ public class SocketClient {
                     buyGuardMsgBean.setGuardType(map.getIntValue("guard_type"));
                     buyGuardMsgBean.setGuardType(map.getIntValue("guard_type"));
                     mListener.onBuyGuard(buyGuardMsgBean);
                     mListener.onBuyGuard(buyGuardMsgBean);
                     break;
                     break;
-                case Constants.SOCKET_LINK_MIC://连麦
+                case Constants.SOCKET_LINK_MIC:// 连麦
                     processLinkMic(map);
                     processLinkMic(map);
                     break;
                     break;
-                case Constants.SOCKET_LINK_MIC_ANCHOR://主播连麦
+                case Constants.SOCKET_LINK_MIC_ANCHOR:// 主播连麦
                     processLinkMicAnchor(map);
                     processLinkMicAnchor(map);
                     break;
                     break;
-                case Constants.SOCKET_LINK_MIC_PK://主播PK
+                case Constants.SOCKET_LINK_MIC_PK:// 主播PK
                     processAnchorLinkMicPk(map);
                     processAnchorLinkMicPk(map);
                     break;
                     break;
-                case Constants.SOCKET_RED_PACK://红包消息
+                case Constants.SOCKET_RED_PACK:// 红包消息
                     String uid = map.getString("uid");
                     String uid = map.getString("uid");
                     if (TextUtils.isEmpty(uid)) {
                     if (TextUtils.isEmpty(uid)) {
                         return;
                         return;
@@ -416,7 +418,8 @@ public class SocketClient {
                     LiveChatBean liveChatBean = new LiveChatBean();
                     LiveChatBean liveChatBean = new LiveChatBean();
                     liveChatBean.setType(LiveChatBean.RED_PACK);
                     liveChatBean.setType(LiveChatBean.RED_PACK);
                     liveChatBean.setId(uid);
                     liveChatBean.setId(uid);
-                    String name = uid.equals(mLiveUid) ? WordUtil.getString(R.string.live_anchor) : map.getString("uname");
+                    String name = uid.equals(mLiveUid) ? WordUtil.getString(R.string.live_anchor)
+                            : map.getString("uname");
                     if (LanguageUtil.isZh()) {
                     if (LanguageUtil.isZh()) {
                         liveChatBean.setContent(StringUtil.contact(name, getLanguageCt(map)));
                         liveChatBean.setContent(StringUtil.contact(name, getLanguageCt(map)));
                     } else {
                     } else {
@@ -425,14 +428,14 @@ public class SocketClient {
                     mListener.onRedPack(liveChatBean);
                     mListener.onRedPack(liveChatBean);
                     break;
                     break;
 
 
-                case Constants.SOCKET_LUCK_WIN://幸运礼物中奖
+                case Constants.SOCKET_LUCK_WIN:// 幸运礼物中奖
                     mListener.onLuckGiftWin(map.toJavaObject(LiveLuckGiftWinBean.class));
                     mListener.onLuckGiftWin(map.toJavaObject(LiveLuckGiftWinBean.class));
                     break;
                     break;
 
 
-                case Constants.SOCKET_PRIZE_POOL_WIN://奖池中奖
+                case Constants.SOCKET_PRIZE_POOL_WIN:// 奖池中奖
                     mListener.onPrizePoolWin(map.toJavaObject(LiveGiftPrizePoolWinBean.class));
                     mListener.onPrizePoolWin(map.toJavaObject(LiveGiftPrizePoolWinBean.class));
                     break;
                     break;
-                case Constants.SOCKET_PRIZE_POOL_UP://奖池升级
+                case Constants.SOCKET_PRIZE_POOL_UP:// 奖池升级
                     mListener.onPrizePoolUp(map.getString("uplevel"));
                     mListener.onPrizePoolUp(map.getString("uplevel"));
                     break;
                     break;
                 case Constants.SOCKET_LIVE_GOODS_SHOW:
                 case Constants.SOCKET_LIVE_GOODS_SHOW:
@@ -452,7 +455,6 @@ public class SocketClient {
             }
             }
         }
         }
 
 
-
         /**
         /**
          * 接收到系统消息,显示在聊天栏中
          * 接收到系统消息,显示在聊天栏中
          */
          */
@@ -469,48 +471,48 @@ public class SocketClient {
         private void processLinkMic(JSONObject map) {
         private void processLinkMic(JSONObject map) {
             int action = map.getIntValue("action");
             int action = map.getIntValue("action");
             switch (action) {
             switch (action) {
-                case 1://主播收到观众连麦的申请
+                case 1:// 主播收到观众连麦的申请
                     UserBean u = new UserBean();
                     UserBean u = new UserBean();
                     u.setId(map.getString("uid"));
                     u.setId(map.getString("uid"));
                     u.setUserNiceName(map.getString("uname"));
                     u.setUserNiceName(map.getString("uname"));
                     u.setAvatar(map.getString("uhead"));
                     u.setAvatar(map.getString("uhead"));
                     u.setSex(map.getIntValue("sex"));
                     u.setSex(map.getIntValue("sex"));
-                    //u.setLevel(map.getIntValue("level"));
+                    // u.setLevel(map.getIntValue("level"));
                     mListener.onAudienceApplyLinkMic(u);
                     mListener.onAudienceApplyLinkMic(u);
                     break;
                     break;
-                case 2://观众收到主播同意连麦的消息
+                case 2:// 观众收到主播同意连麦的消息
                     if (map.getString("touid").equals(CommonAppConfig.getInstance().getUid())) {
                     if (map.getString("touid").equals(CommonAppConfig.getInstance().getUid())) {
                         mListener.onAnchorAcceptLinkMic();
                         mListener.onAnchorAcceptLinkMic();
                     }
                     }
                     break;
                     break;
-                case 3://观众收到主播拒绝连麦的消息
+                case 3:// 观众收到主播拒绝连麦的消息
                     if (map.getString("touid").equals(CommonAppConfig.getInstance().getUid())) {
                     if (map.getString("touid").equals(CommonAppConfig.getInstance().getUid())) {
                         mListener.onAnchorRefuseLinkMic();
                         mListener.onAnchorRefuseLinkMic();
                     }
                     }
                     break;
                     break;
-                case 4://所有人收到连麦观众发过来的流地址
+                case 4:// 所有人收到连麦观众发过来的流地址
                     String uid = map.getString("uid");
                     String uid = map.getString("uid");
                     if (!TextUtils.isEmpty(uid) && !uid.equals(CommonAppConfig.getInstance().getUid())) {
                     if (!TextUtils.isEmpty(uid) && !uid.equals(CommonAppConfig.getInstance().getUid())) {
                         mListener.onAudienceSendLinkMicUrl(uid, map.getString("uname"), map.getString("playurl"));
                         mListener.onAudienceSendLinkMicUrl(uid, map.getString("uname"), map.getString("playurl"));
                     }
                     }
                     break;
                     break;
-                case 5://连麦观众自己断开连麦
+                case 5:// 连麦观众自己断开连麦
                     mListener.onAudienceCloseLinkMic(map.getString("uid"), map.getString("uname"));
                     mListener.onAudienceCloseLinkMic(map.getString("uid"), map.getString("uname"));
                     break;
                     break;
-                case 6://主播断开已连麦观众的连麦
+                case 6:// 主播断开已连麦观众的连麦
                     mListener.onAnchorCloseLinkMic(map.getString("touid"), map.getString("uname"));
                     mListener.onAnchorCloseLinkMic(map.getString("touid"), map.getString("uname"));
                     break;
                     break;
-                case 7://已申请连麦的观众收到主播繁忙的消息
+                case 7:// 已申请连麦的观众收到主播繁忙的消息
                     if (map.getString("touid").equals(CommonAppConfig.getInstance().getUid())) {
                     if (map.getString("touid").equals(CommonAppConfig.getInstance().getUid())) {
                         mListener.onAnchorBusy();
                         mListener.onAnchorBusy();
                     }
                     }
                     break;
                     break;
-                case 8://已申请连麦的观众收到主播无响应的消息
+                case 8:// 已申请连麦的观众收到主播无响应的消息
                     if (map.getString("touid").equals(CommonAppConfig.getInstance().getUid())) {
                     if (map.getString("touid").equals(CommonAppConfig.getInstance().getUid())) {
                         mListener.onAnchorNotResponse();
                         mListener.onAnchorNotResponse();
                     }
                     }
                     break;
                     break;
-                case 9://所有人收到已连麦的观众退出直播间消息
+                case 9:// 所有人收到已连麦的观众退出直播间消息
                     mListener.onAudienceLinkMicExitRoom(map.getString("touid"));
                     mListener.onAudienceLinkMicExitRoom(map.getString("touid"));
                     break;
                     break;
             }
             }
@@ -524,32 +526,51 @@ public class SocketClient {
         private void processLinkMicAnchor(JSONObject map) {
         private void processLinkMicAnchor(JSONObject map) {
             int action = map.getIntValue("action");
             int action = map.getIntValue("action");
             switch (action) {
             switch (action) {
-                case 1://收到其他主播连麦的邀请的回调
+                case 1:// 收到其他主播连麦的邀请的回调
                     UserBean u = new UserBean();
                     UserBean u = new UserBean();
                     u.setId(map.getString("uid"));
                     u.setId(map.getString("uid"));
                     u.setUserNiceName(map.getString("uname"));
                     u.setUserNiceName(map.getString("uname"));
                     u.setAvatar(map.getString("uhead"));
                     u.setAvatar(map.getString("uhead"));
                     u.setSex(map.getIntValue("sex"));
                     u.setSex(map.getIntValue("sex"));
-                    //u.setLevel(map.getIntValue("level"));
-                    //u.setLevelAnchor(map.getIntValue("level_anchor"));
+                    // u.setLevel(map.getIntValue("level"));
+                    // u.setLevelAnchor(map.getIntValue("level_anchor"));
                     mListener.onLinkMicAnchorApply(u, map.getString("stream"));
                     mListener.onLinkMicAnchorApply(u, map.getString("stream"));
                     break;
                     break;
-                case 3://对方主播拒绝连麦的回调
+                case 3:// 对方主播拒绝连麦的回调
                     mListener.onLinkMicAnchorRefuse();
                     mListener.onLinkMicAnchorRefuse();
                     break;
                     break;
-                case 4://所有人收到对方主播的播流地址的回调
-                    mListener.onLinkMicAnchorPlayUrl(map.getString("pkuid"), map.getString("pkpull"));
+                case 4: // 多人连麦通知
+                    //roomMembers中存的是map,进行解析
+                    Map<String, String>[] roomMembers = JSON.parseObject(map.getString("room_members"),
+                            Map[].class);
+
+//                    String[] roomMembers = map.getObject("room_members", String[].class);
+                    if (roomMembers == null || roomMembers.length == 0) {
+                        mListener.onLinkMicAnchorPlayUrl(map.getString("pkuid"), map.getString("pkpull"));
+                        return;
+                    }
+                    L.e(TAG, "--多人连麦通知-->" + roomMembers.length);
+                    for (Map<String, String> roomMember : roomMembers) {
+                        String uid = roomMember.get("uid");
+                        String pull = roomMember.get("pull");
+                        // uid 和当前用户uid进行比较,如果不相等,则是其他主播的播流地址
+                        if (!uid.equals(CommonAppConfig.getInstance().getUid())) {
+                            mListener.onLinkMicAnchorPlayUrl(uid, pull);
+                        }
+                    }
                     break;
                     break;
-                case 5://断开连麦的回调
-                    mListener.onLinkMicAnchorClose();
+                case 5:// 断开连麦的回调
+//                    mListener.onLinkMicAnchorClose();
+                    String uid = map.getString("uid");
+                    mListener.onMultiLinkMicAnchorLeave(uid);
                     break;
                     break;
-                case 7://对方主播正在忙的回调
+                case 7:// 对方主播正在忙的回调
                     mListener.onLinkMicAnchorBusy();
                     mListener.onLinkMicAnchorBusy();
                     break;
                     break;
-                case 8://对方主播无响应的回调
+                case 8:// 对方主播无响应的回调
                     mListener.onLinkMicAnchorNotResponse();
                     mListener.onLinkMicAnchorNotResponse();
                     break;
                     break;
-                case 9://对方主播正在游戏
+                case 9:// 对方主播正在游戏
                     mListener.onlinkMicPlayGaming();
                     mListener.onlinkMicPlayGaming();
                     break;
                     break;
             }
             }
@@ -563,32 +584,32 @@ public class SocketClient {
         private void processAnchorLinkMicPk(JSONObject map) {
         private void processAnchorLinkMicPk(JSONObject map) {
             int action = map.getIntValue("action");
             int action = map.getIntValue("action");
             switch (action) {
             switch (action) {
-                case 1://收到对方主播PK回调
+                case 1:// 收到对方主播PK回调
                     UserBean u = new UserBean();
                     UserBean u = new UserBean();
                     u.setId(map.getString("uid"));
                     u.setId(map.getString("uid"));
                     u.setUserNiceName(map.getString("uname"));
                     u.setUserNiceName(map.getString("uname"));
                     u.setAvatar(map.getString("uhead"));
                     u.setAvatar(map.getString("uhead"));
                     u.setSex(map.getIntValue("sex"));
                     u.setSex(map.getIntValue("sex"));
-                    //u.setLevel(map.getIntValue("level"));
-                    //u.setLevelAnchor(map.getIntValue("level_anchor"));
+                    // u.setLevel(map.getIntValue("level"));
+                    // u.setLevelAnchor(map.getIntValue("level_anchor"));
                     mListener.onLinkMicPkApply(u, map.getString("stream"));
                     mListener.onLinkMicPkApply(u, map.getString("stream"));
                     break;
                     break;
-                case 3://对方主播拒绝PK的回调
+                case 3:// 对方主播拒绝PK的回调
                     mListener.onLinkMicPkRefuse();
                     mListener.onLinkMicPkRefuse();
                     break;
                     break;
-                case 4://所有人收到PK开始址的回调
+                case 4:// 所有人收到PK开始址的回调
                     mListener.onLinkMicPkStart(map.getString("pkuid"));
                     mListener.onLinkMicPkStart(map.getString("pkuid"));
                     break;
                     break;
-                case 5://PK时候断开连麦的回调
+                case 5:// PK时候断开连麦的回调
                     mListener.onLinkMicPkClose();
                     mListener.onLinkMicPkClose();
                     break;
                     break;
-                case 7://对方主播正在忙的回调
+                case 7:// 对方主播正在忙的回调
                     mListener.onLinkMicPkBusy();
                     mListener.onLinkMicPkBusy();
                     break;
                     break;
-                case 8://对方主播无响应的回调
+                case 8:// 对方主播无响应的回调
                     mListener.onLinkMicPkNotResponse();
                     mListener.onLinkMicPkNotResponse();
                     break;
                     break;
-                case 9://pk结束的回调
+                case 9:// pk结束的回调
                     mListener.onLinkMicPkEnd(map.getString("win_uid"));
                     mListener.onLinkMicPkEnd(map.getString("win_uid"));
                     break;
                     break;
             }
             }

+ 40 - 3
ybvideoandroid/live/src/main/java/com/yunbao/live/socket/SocketLinkMicAnchorUtil.java

@@ -42,7 +42,6 @@ public class SocketLinkMicAnchorUtil {
                 .param("pkuid", pkUid));
                 .param("pkuid", pkUid));
     }
     }
 
 
-
     /**
     /**
      * 主播接受其他主播的连麦请求
      * 主播接受其他主播的连麦请求
      *
      *
@@ -90,7 +89,6 @@ public class SocketLinkMicAnchorUtil {
                 .param("ct", ""));
                 .param("ct", ""));
     }
     }
 
 
-
     /**
     /**
      * 主播断开连麦
      * 主播断开连麦
      */
      */
@@ -140,7 +138,6 @@ public class SocketLinkMicAnchorUtil {
                 .param("pkuid", pkUid));
                 .param("pkuid", pkUid));
     }
     }
 
 
-
     /**
     /**
      * 当收到主播连麦的请求时候对方主播正在游戏中
      * 当收到主播连麦的请求时候对方主播正在游戏中
      */
      */
@@ -155,4 +152,44 @@ public class SocketLinkMicAnchorUtil {
                 .param("pkuid", pkUid));
                 .param("pkuid", pkUid));
     }
     }
 
 
+    /**
+     * 多人连麦成功通知处理
+     *
+     * @param client      Socket客户端
+     * @param roomMembers 房间成员列表
+     * @param newUid      新加入的主播uid
+     * @param inviterUid  邀请者uid
+     * @param actionType  操作类型:join/leave
+     */
+    public static void handleMultiLinkMicNotify(SocketClient client, String[] roomMembers, String newUid,
+                                                String inviterUid, String actionType) {
+        if (client == null) {
+            return;
+        }
+        UserBean u = CommonAppConfig.getInstance().getUserBean();
+        if (u == null) {
+            return;
+        }
+
+        // 将String[]转换为JSON字符串
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        for (int i = 0; i < roomMembers.length; i++) {
+            sb.append("\"").append(roomMembers[i]).append("\"");
+            if (i < roomMembers.length - 1) {
+                sb.append(",");
+            }
+        }
+        sb.append("]");
+
+        client.send(new SocketSendBean()
+                .param("_method_", Constants.SOCKET_LINK_MIC_ANCHOR)
+                .param("action", 4)
+                .param("msgtype", 10)
+                .param("uid", u.getId())
+                .paramJsonArray("room_members", sb.toString())
+                .param("new_uid", newUid)
+                .param("inviter_uid", inviterUid)
+                .param("action_type", actionType));
+    }
 }
 }

+ 6 - 0
ybvideoandroid/live/src/main/java/com/yunbao/live/socket/SocketMessageListener.java

@@ -12,6 +12,7 @@ import com.yunbao.common.bean.LiveReceiveGiftBean;
 import com.yunbao.live.bean.LiveUserGiftBean;
 import com.yunbao.live.bean.LiveUserGiftBean;
 
 
 import java.util.List;
 import java.util.List;
+import java.util.Map;
 
 
 /**
 /**
  * Created by cxf on 2017/8/22.
  * Created by cxf on 2017/8/22.
@@ -199,6 +200,11 @@ public interface SocketMessageListener {
     void onLinkMicAnchorClose();
     void onLinkMicAnchorClose();
 
 
     /**
     /**
+     * 主播与主播多人连麦,有主播离开
+     */
+    void onMultiLinkMicAnchorLeave(String uid);
+
+    /**
      * 主播与主播连麦  对方主播拒绝连麦pk的回调
      * 主播与主播连麦  对方主播拒绝连麦pk的回调
      */
      */
     void onLinkMicAnchorRefuse();
     void onLinkMicAnchorRefuse();

+ 6 - 0
ybvideoandroid/live/src/main/java/com/yunbao/live/views/AbsLivePushViewHolder.java

@@ -31,6 +31,7 @@ public abstract class AbsLivePushViewHolder extends AbsViewHolder implements ILi
     protected ViewGroup mSmallContainer;
     protected ViewGroup mSmallContainer;
     protected ViewGroup mLeftContainer;
     protected ViewGroup mLeftContainer;
     protected ViewGroup mRightContainer;
     protected ViewGroup mRightContainer;
+    protected ViewGroup mBottomRightContainer;
     protected ViewGroup mPkContainer;
     protected ViewGroup mPkContainer;
     protected View mPreView;
     protected View mPreView;
     protected boolean mOpenCamera;//是否选择了相机
     protected boolean mOpenCamera;//是否选择了相机
@@ -55,6 +56,7 @@ public abstract class AbsLivePushViewHolder extends AbsViewHolder implements ILi
         mSmallContainer = (ViewGroup) findViewById(R.id.small_container);
         mSmallContainer = (ViewGroup) findViewById(R.id.small_container);
         mLeftContainer = (ViewGroup) findViewById(R.id.left_container);
         mLeftContainer = (ViewGroup) findViewById(R.id.left_container);
         mRightContainer = (ViewGroup) findViewById(R.id.right_container);
         mRightContainer = (ViewGroup) findViewById(R.id.right_container);
+        mBottomRightContainer = (ViewGroup) findViewById(R.id.bottom_right_container);
         mPkContainer = (ViewGroup) findViewById(R.id.pk_container);
         mPkContainer = (ViewGroup) findViewById(R.id.pk_container);
         mCameraFront = true;
         mCameraFront = true;
     }
     }
@@ -111,6 +113,10 @@ public abstract class AbsLivePushViewHolder extends AbsViewHolder implements ILi
     public ViewGroup getRightContainer() {
     public ViewGroup getRightContainer() {
         return mRightContainer;
         return mRightContainer;
     }
     }
+    @Override
+    public ViewGroup getBottomRightContainer() {
+        return mBottomRightContainer;
+    }
 
 
     @Override
     @Override
     public ViewGroup getPkContainer() {
     public ViewGroup getPkContainer() {

+ 19 - 0
ybvideoandroid/live/src/main/java/com/yunbao/live/views/LiveAnchorViewHolder.java

@@ -24,6 +24,7 @@ public class LiveAnchorViewHolder extends AbsLiveViewHolder {
     private ImageView mBtnFunction;
     private ImageView mBtnFunction;
     private View mBtnGameClose;//关闭游戏的按钮
     private View mBtnGameClose;//关闭游戏的按钮
     private View mBtnPk;//主播连麦pk按钮
     private View mBtnPk;//主播连麦pk按钮
+    private View mBtnExitLinkMic; // 退出连麦按钮
     private Drawable mDrawable0;
     private Drawable mDrawable0;
     private Drawable mDrawable1;
     private Drawable mDrawable1;
     private Drawable mDrawableLinkMic0;//允许连麦
     private Drawable mDrawableLinkMic0;//允许连麦
@@ -64,6 +65,8 @@ public class LiveAnchorViewHolder extends AbsLiveViewHolder {
         findViewById(R.id.btn_link_mic).setOnClickListener(this);
         findViewById(R.id.btn_link_mic).setOnClickListener(this);
         mBtnShop=findViewById(R.id.btn_shop);
         mBtnShop=findViewById(R.id.btn_shop);
         mBtnShop.setOnClickListener(this);
         mBtnShop.setOnClickListener(this);
+        mBtnExitLinkMic = findViewById(R.id.btn_exit_link_mic);
+        mBtnExitLinkMic.setOnClickListener(this);
     }
     }
 
 
     @Override
     @Override
@@ -90,10 +93,26 @@ public class LiveAnchorViewHolder extends AbsLiveViewHolder {
 
 
         }else if (i == R.id.btn_shop) {
         }else if (i == R.id.btn_shop) {
             ((LiveAnchorActivity) mContext).openGoodsWindow();
             ((LiveAnchorActivity) mContext).openGoodsWindow();
+        } else if (i == R.id.btn_exit_link_mic) {
+            exitLinkMic();
         }
         }
     }
     }
 
 
+    /**
+     * 设置退出连麦按钮是否可见
+     */
+    public void setExitLinkMicBtnVisible(boolean visible) {
+        if (mBtnExitLinkMic != null) {
+            mBtnExitLinkMic.setVisibility(visible ? View.VISIBLE : View.GONE);
+        }
+    }
 
 
+    /**
+     * 退出连麦
+     */
+    private void exitLinkMic() {
+        ((LiveAnchorActivity) mContext).exitLinkMic();
+    }
 
 
     public void setShopBtnVisible(boolean show) {
     public void setShopBtnVisible(boolean show) {
         if (mBtnShop != null) {
         if (mBtnShop != null) {

+ 84 - 13
ybvideoandroid/live/src/main/java/com/yunbao/live/views/LiveLinkMicPlayTxViewHolder.java

@@ -3,9 +3,11 @@ package com.yunbao.live.views;
 import android.content.Context;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.text.TextUtils;
+import android.view.Gravity;
 import android.view.TextureView;
 import android.view.TextureView;
 import android.view.View;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.TextView;
 
 
@@ -21,6 +23,7 @@ import com.yunbao.common.http.CommonHttpUtil;
 import com.yunbao.common.http.HttpCallback;
 import com.yunbao.common.http.HttpCallback;
 import com.yunbao.common.http.JsonBean;
 import com.yunbao.common.http.JsonBean;
 import com.yunbao.common.interfaces.CommonCallback;
 import com.yunbao.common.interfaces.CommonCallback;
+import com.yunbao.common.utils.DpUtil;
 import com.yunbao.common.utils.L;
 import com.yunbao.common.utils.L;
 import com.yunbao.common.utils.ToastUtil;
 import com.yunbao.common.utils.ToastUtil;
 import com.yunbao.live.R;
 import com.yunbao.live.R;
@@ -28,7 +31,7 @@ import com.yunbao.live.http.LiveHttpUtil;
 
 
 /**
 /**
  * Created by cxf on 2018/10/25.
  * Created by cxf on 2018/10/25.
- * 连麦播放小窗口  使用腾讯sdk
+ * 连麦播放小窗口 使用腾讯sdk
  */
  */
 
 
 public class LiveLinkMicPlayTxViewHolder extends AbsLiveLinkMicPlayViewHolder {
 public class LiveLinkMicPlayTxViewHolder extends AbsLiveLinkMicPlayViewHolder {
@@ -68,7 +71,7 @@ public class LiveLinkMicPlayTxViewHolder extends AbsLiveLinkMicPlayViewHolder {
             @Override
             @Override
             public void onError(V2TXLivePlayer player, int code, String msg, Bundle extraInfo) {
             public void onError(V2TXLivePlayer player, int code, String msg, Bundle extraInfo) {
                 L.e(TAG, "V2TXLivePlayerObserver--onError: code=" + code + ", msg= " + msg);
                 L.e(TAG, "V2TXLivePlayerObserver--onError: code=" + code + ", msg= " + msg);
-//                ToastUtil.show(WordUtil.getString(R.string.live_play_error));
+                // ToastUtil.show(WordUtil.getString(R.string.live_play_error));
             }
             }
 
 
             @Override
             @Override
@@ -110,7 +113,8 @@ public class LiveLinkMicPlayTxViewHolder extends AbsLiveLinkMicPlayViewHolder {
     @Override
     @Override
     public void setOnCloseListener(View.OnClickListener onClickListener) {
     public void setOnCloseListener(View.OnClickListener onClickListener) {
         if (onClickListener != null) {
         if (onClickListener != null) {
-            mBtnClose.setVisibility(View.VISIBLE);
+//            mBtnClose.setVisibility(View.VISIBLE);
+            mBtnClose.setVisibility(View.GONE);
             mBtnClose.setOnClickListener(onClickListener);
             mBtnClose.setOnClickListener(onClickListener);
         }
         }
     }
     }
@@ -181,31 +185,30 @@ public class LiveLinkMicPlayTxViewHolder extends AbsLiveLinkMicPlayViewHolder {
     public void showUserMessage(String pkuid) {
     public void showUserMessage(String pkuid) {
         mGroup.setVisibility(View.VISIBLE);
         mGroup.setVisibility(View.VISIBLE);
         getLiveFollow(pkuid);
         getLiveFollow(pkuid);
-        pkUid=pkuid;
+        pkUid = pkuid;
     }
     }
 
 
-
     /**
     /**
      * 获取对方主播信息
      * 获取对方主播信息
      */
      */
 
 
-    private void getLiveFollow(String mPkUid){
+    private void getLiveFollow(String mPkUid) {
         LiveHttpUtil.getLiveFollow(mPkUid, new HttpCallback() {
         LiveHttpUtil.getLiveFollow(mPkUid, new HttpCallback() {
             @Override
             @Override
             public void onSuccess(int code, String msg, String[] info) {
             public void onSuccess(int code, String msg, String[] info) {
-                if (code==0){
+                if (code == 0) {
                     JSONObject obj = JSON.parseObject(info[0]);
                     JSONObject obj = JSON.parseObject(info[0]);
-                    int isattention =obj.getIntValue("isattention");
-                    String avatar=obj.getString("avatar");
-                    String username=obj.getString("user_nickname");
+                    int isattention = obj.getIntValue("isattention");
+                    String avatar = obj.getString("avatar");
+                    String username = obj.getString("user_nickname");
                     ImgLoader.displayAvatar(mContext, avatar, mAvatar);
                     ImgLoader.displayAvatar(mContext, avatar, mAvatar);
                     mName.setText(username);
                     mName.setText(username);
-                    if (isattention==1){
+                    if (isattention == 1) {
                         mBtnFollow.setVisibility(View.GONE);
                         mBtnFollow.setVisibility(View.GONE);
-                    }else{
+                    } else {
                         mBtnFollow.setVisibility(View.VISIBLE);
                         mBtnFollow.setVisibility(View.VISIBLE);
                     }
                     }
-                }else{
+                } else {
                     ToastUtil.show(msg);
                     ToastUtil.show(msg);
                 }
                 }
 
 
@@ -229,4 +232,72 @@ public class LiveLinkMicPlayTxViewHolder extends AbsLiveLinkMicPlayViewHolder {
             }
             }
         });
         });
     }
     }
+
+//    /**
+//     * 更新多人连麦布局
+//     *
+//     * @param index      当前窗口索引
+//     * @param totalCount 总连麦人数
+//     */
+//    public void updateLayoutForMultiLinkMic(int index, int totalCount) {
+//        if (mContentView == null)
+//            return;
+//        L.e(TAG, "更新多人连麦布局updateLayoutForMultiLinkMic: index=" + index + ", totalCount=" + totalCount);
+//        ViewGroup.LayoutParams params = mContentView.getLayoutParams();
+//        if (params instanceof ViewGroup.MarginLayoutParams) {
+//            ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) params;
+//
+//            if (totalCount == 2) {
+//                // 2人连麦布局:基于现有一对一连麦布局
+//                int windowHeight = DpUtil.dp2px(160);
+//                int windowWidth = DpUtil.dp2px(120);
+//
+//                params.width = windowWidth;
+//                params.height = windowHeight;
+//                marginParams.topMargin = DpUtil.dp2px(130);
+//                marginParams.rightMargin = DpUtil.dp2px(10);
+//
+//                if (params instanceof FrameLayout.LayoutParams) {
+//                    ((FrameLayout.LayoutParams) params).gravity = Gravity.TOP | Gravity.RIGHT;
+//                }
+//            } else if (totalCount == 3) {
+//                // 3人连麦布局:上面两个窗口与两人连麦布局相同,第三个窗口在下方
+//                int windowHeight = DpUtil.dp2px(160); // 与两人连麦相同
+//                int windowWidth = DpUtil.dp2px(120);  // 与两人连麦相同
+//                int margin = DpUtil.dp2px(10);        // 与两人连麦相同的边距
+//
+//                params.width = windowWidth;
+//                params.height = windowHeight;
+//                L.e(TAG, "更新多人连麦布局updateLayoutForMultiLinkMic: index=" + index);
+//                if (index == 0) {
+//                    // 第一个窗口:右上角(与两人连麦完全相同)
+//                    marginParams.topMargin = DpUtil.dp2px(130);
+//                    marginParams.rightMargin = margin;
+//                    marginParams.leftMargin = 0; // 清除左边距
+//                    if (params instanceof FrameLayout.LayoutParams) {
+//                        ((FrameLayout.LayoutParams) params).gravity = Gravity.TOP | Gravity.RIGHT;
+//                    }
+//                } else if (index == 1) {
+//                    // 第二个窗口:左上角(镜像右上角的布局)
+//                    marginParams.topMargin = DpUtil.dp2px(130);
+//                    marginParams.leftMargin = margin;
+//                    marginParams.rightMargin = 0; // 清除右边距
+//                    if (params instanceof FrameLayout.LayoutParams) {
+//                        ((FrameLayout.LayoutParams) params).gravity = Gravity.TOP | Gravity.LEFT;
+//                    }
+//                } else if (index == 2) {
+//                    // 第三个窗口:下方居中
+//                    marginParams.topMargin = DpUtil.dp2px(130) + windowHeight + margin; // 130 + 160 + 10 = 300dp
+//                    marginParams.leftMargin = 0;
+//                    marginParams.rightMargin = 0;
+//                    if (params instanceof FrameLayout.LayoutParams) {
+//                        // 使用水平居中对齐
+//                        ((FrameLayout.LayoutParams) params).gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+//                    }
+//                }
+//            }
+//
+//            mContentView.setLayoutParams(params);
+//        }
+//    }
 }
 }

+ 78 - 59
ybvideoandroid/live/src/main/java/com/yunbao/live/views/LivePlayTxViewHolder.java

@@ -30,6 +30,7 @@ import com.yunbao.common.glide.ImgLoader;
 import com.yunbao.common.http.HttpCallback;
 import com.yunbao.common.http.HttpCallback;
 import com.yunbao.common.utils.DpUtil;
 import com.yunbao.common.utils.DpUtil;
 import com.yunbao.common.utils.L;
 import com.yunbao.common.utils.L;
+import com.yunbao.common.utils.ScreenDimenUtil;
 import com.yunbao.common.utils.ToastUtil;
 import com.yunbao.common.utils.ToastUtil;
 import com.yunbao.common.utils.WordUtil;
 import com.yunbao.common.utils.WordUtil;
 import com.yunbao.live.R;
 import com.yunbao.live.R;
@@ -40,7 +41,7 @@ import com.yunbao.live.http.LiveHttpUtil;
 
 
 /**
 /**
  * Created by cxf on 2018/10/10.
  * Created by cxf on 2018/10/10.
- * 直播间播放器  腾讯播放器
+ * 直播间播放器 腾讯播放器
  */
  */
 
 
 public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
 public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
@@ -50,6 +51,7 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
     private ViewGroup mSmallContainer;
     private ViewGroup mSmallContainer;
     private ViewGroup mLeftContainer;
     private ViewGroup mLeftContainer;
     private ViewGroup mRightContainer;
     private ViewGroup mRightContainer;
+    private ViewGroup mBottomRightContainer;
     private ViewGroup mPkContainer;
     private ViewGroup mPkContainer;
     private TextureView mLiveView;
     private TextureView mLiveView;
     private TextureRenderView mVideoView;
     private TextureRenderView mVideoView;
@@ -57,19 +59,18 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
     private ImageView mCover;
     private ImageView mCover;
     private V2TXLivePlayer mLivePlayer;
     private V2TXLivePlayer mLivePlayer;
     private TXVodPlayer mVodPlayer;
     private TXVodPlayer mVodPlayer;
-    private boolean mPaused;//是否切后台了
-    private boolean mPausedPlay;//是否被动暂停了播放
+    private boolean mPaused;// 是否切后台了
+    private boolean mPausedPlay;// 是否被动暂停了播放
     private boolean mChangeToLeft;
     private boolean mChangeToLeft;
     private boolean mChangeToAnchorLinkMic;
     private boolean mChangeToAnchorLinkMic;
     private String mUrl;
     private String mUrl;
     private Handler mHandler;
     private Handler mHandler;
-//    private int mVideoLastProgress;
+    // private int mVideoLastProgress;
     private float mVideoWidth;
     private float mVideoWidth;
     private float mVideoHeight;
     private float mVideoHeight;
-//    private int mRootHeight;
+    // private int mRootHeight;
     private Boolean mIsLive;
     private Boolean mIsLive;
 
 
-
     public LivePlayTxViewHolder(Context context, ViewGroup parentView) {
     public LivePlayTxViewHolder(Context context, ViewGroup parentView) {
         super(context, parentView);
         super(context, parentView);
     }
     }
@@ -82,15 +83,16 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
     @Override
     @Override
     public void init() {
     public void init() {
         mRoot = (ViewGroup) findViewById(R.id.root);
         mRoot = (ViewGroup) findViewById(R.id.root);
-//        mRoot.post(new Runnable() {
-//            @Override
-//            public void run() {
-//                mRootHeight = mRoot.getHeight();
-//            }
-//        });
+        // mRoot.post(new Runnable() {
+        // @Override
+        // public void run() {
+        // mRootHeight = mRoot.getHeight();
+        // }
+        // });
         mSmallContainer = (ViewGroup) findViewById(R.id.small_container);
         mSmallContainer = (ViewGroup) findViewById(R.id.small_container);
         mLeftContainer = (ViewGroup) findViewById(R.id.left_container);
         mLeftContainer = (ViewGroup) findViewById(R.id.left_container);
         mRightContainer = (ViewGroup) findViewById(R.id.right_container);
         mRightContainer = (ViewGroup) findViewById(R.id.right_container);
+        mBottomRightContainer = (ViewGroup) findViewById(R.id.bottom_right_container);
         mPkContainer = (ViewGroup) findViewById(R.id.pk_container);
         mPkContainer = (ViewGroup) findViewById(R.id.pk_container);
         mLoading = findViewById(R.id.loading);
         mLoading = findViewById(R.id.loading);
         mCover = (ImageView) findViewById(R.id.cover);
         mCover = (ImageView) findViewById(R.id.cover);
@@ -98,7 +100,6 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
         mVideoView = (TextureRenderView) findViewById(R.id.video_view);
         mVideoView = (TextureRenderView) findViewById(R.id.video_view);
     }
     }
 
 
-
     private V2TXLivePlayer getLivePlayer() {
     private V2TXLivePlayer getLivePlayer() {
         if (mLivePlayer == null) {
         if (mLivePlayer == null) {
             mLivePlayer = new V2TXLivePlayerImpl(mContext);
             mLivePlayer = new V2TXLivePlayerImpl(mContext);
@@ -115,7 +116,8 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
 
 
                 @Override
                 @Override
                 public void onVideoResolutionChanged(V2TXLivePlayer player, int width, int height) {
                 public void onVideoResolutionChanged(V2TXLivePlayer player, int width, int height) {
-                    L.e(TAG, "V2TXLivePlayerObserver--onVideoResolutionChanged: width=" + width + ", height= " + height);
+                    L.e(TAG, "V2TXLivePlayerObserver--onVideoResolutionChanged: width=" + width + ", height= "
+                            + height);
                     if (mChangeToLeft || mChangeToAnchorLinkMic) {
                     if (mChangeToLeft || mChangeToAnchorLinkMic) {
                         return;
                         return;
                     }
                     }
@@ -166,16 +168,16 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
             mVodPlayer.setVodListener(new ITXVodPlayListener() {
             mVodPlayer.setVodListener(new ITXVodPlayListener() {
                 @Override
                 @Override
                 public void onPlayEvent(TXVodPlayer txVodPlayer, int e, Bundle bundle) {
                 public void onPlayEvent(TXVodPlayer txVodPlayer, int e, Bundle bundle) {
-//                    if (e != 2005) {
-//                        L.e(TAG, "------onPlayEvent----->" + e);
-//                    }
+                    // if (e != 2005) {
+                    // L.e(TAG, "------onPlayEvent----->" + e);
+                    // }
                     switch (e) {
                     switch (e) {
                         case TXLiveConstants.PLAY_EVT_VOD_PLAY_PREPARED:
                         case TXLiveConstants.PLAY_EVT_VOD_PLAY_PREPARED:
                             if (mVodPlayer != null) {
                             if (mVodPlayer != null) {
                                 mVodPlayer.resume();
                                 mVodPlayer.resume();
                             }
                             }
                             break;
                             break;
-                        case TXLiveConstants.PLAY_EVT_PLAY_BEGIN://播放开始
+                        case TXLiveConstants.PLAY_EVT_PLAY_BEGIN:// 播放开始
                             if (mLoading != null && mLoading.getVisibility() == View.VISIBLE) {
                             if (mLoading != null && mLoading.getVisibility() == View.VISIBLE) {
                                 mLoading.setVisibility(View.INVISIBLE);
                                 mLoading.setVisibility(View.INVISIBLE);
                             }
                             }
@@ -185,10 +187,10 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
                                 mLoading.setVisibility(View.VISIBLE);
                                 mLoading.setVisibility(View.VISIBLE);
                             }
                             }
                             break;
                             break;
-                        case TXLiveConstants.PLAY_EVT_RCV_FIRST_I_FRAME://第一帧
+                        case TXLiveConstants.PLAY_EVT_RCV_FIRST_I_FRAME:// 第一帧
                             hideCover();
                             hideCover();
                             break;
                             break;
-                        case TXLiveConstants.PLAY_EVT_PLAY_END://播放结束
+                        case TXLiveConstants.PLAY_EVT_PLAY_END:// 播放结束
                             replay();
                             replay();
                             break;
                             break;
                         case TXVodConstants.VOD_PLAY_EVT_CHANGE_RESOLUTION:
                         case TXVodConstants.VOD_PLAY_EVT_CHANGE_RESOLUTION:
@@ -199,18 +201,18 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
                             mVideoHeight = bundle.getInt("EVT_PARAM2", 0);
                             mVideoHeight = bundle.getInt("EVT_PARAM2", 0);
                             changeVideoSize(false);
                             changeVideoSize(false);
                             break;
                             break;
-                        case TXLiveConstants.PLAY_ERR_NET_DISCONNECT://播放失败
+                        case TXLiveConstants.PLAY_ERR_NET_DISCONNECT:// 播放失败
                         case TXLiveConstants.PLAY_ERR_FILE_NOT_FOUND:
                         case TXLiveConstants.PLAY_ERR_FILE_NOT_FOUND:
                             ToastUtil.show(WordUtil.getString(R.string.live_play_error));
                             ToastUtil.show(WordUtil.getString(R.string.live_play_error));
                             break;
                             break;
-//                        case TXLiveConstants.PLAY_EVT_PLAY_PROGRESS:
-//                            int progress = bundle.getInt("EVT_PLAY_PROGRESS_MS");
-//                            if (mVideoLastProgress == progress) {
-//                                replay();
-//                            } else {
-//                                mVideoLastProgress = progress;
-//                            }
-//                            break;
+                        // case TXLiveConstants.PLAY_EVT_PLAY_PROGRESS:
+                        // int progress = bundle.getInt("EVT_PLAY_PROGRESS_MS");
+                        // if (mVideoLastProgress == progress) {
+                        // replay();
+                        // } else {
+                        // mVideoLastProgress = progress;
+                        // }
+                        // break;
                     }
                     }
                 }
                 }
 
 
@@ -224,7 +226,6 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
         return mVodPlayer;
         return mVodPlayer;
     }
     }
 
 
-
     /**
     /**
      * 开始播放
      * 开始播放
      *
      *
@@ -275,7 +276,6 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
         }
         }
     }
     }
 
 
-
     /**
     /**
      * 调整视频播放画面宽高
      * 调整视频播放画面宽高
      */
      */
@@ -293,8 +293,10 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
                 parentWidth = Math.min(p1, p2);
                 parentWidth = Math.min(p1, p2);
                 parentHeight = Math.max(p1, p2);
                 parentHeight = Math.max(p1, p2);
             }
             }
-//            L.e("changeVideoSize", "mVideoWidth----->" + mVideoWidth + "  mVideoHeight------>" + mVideoHeight);
-//            L.e("changeVideoSize", "parentWidth----->" + parentWidth + "  parentHeight------>" + parentHeight);
+            // L.e("changeVideoSize", "mVideoWidth----->" + mVideoWidth + "
+            // mVideoHeight------>" + mVideoHeight);
+            // L.e("changeVideoSize", "parentWidth----->" + parentWidth + "
+            // parentHeight------>" + parentHeight);
             float parentRatio = parentWidth / parentHeight;
             float parentRatio = parentWidth / parentHeight;
             if (videoRatio != parentRatio) {
             if (videoRatio != parentRatio) {
                 FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mVideoView.getLayoutParams();
                 FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mVideoView.getLayoutParams();
@@ -308,14 +310,15 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
                     p.gravity = Gravity.CENTER;
                     p.gravity = Gravity.CENTER;
                 }
                 }
                 mVideoView.requestLayout();
                 mVideoView.requestLayout();
-//                View innerView = mVideoView.getVideoView();
-//                if (innerView != null) {
-//                    ViewGroup.LayoutParams innerLp = innerView.getLayoutParams();
-//                    innerLp.width = ViewGroup.LayoutParams.MATCH_PARENT;
-//                    innerLp.height = ViewGroup.LayoutParams.MATCH_PARENT;
-//                    innerView.setLayoutParams(innerLp);
-//                }
-//                ((LiveAudienceActivity) mContext).onVideoHeightChanged(p.height, mRootHeight);
+                // View innerView = mVideoView.getVideoView();
+                // if (innerView != null) {
+                // ViewGroup.LayoutParams innerLp = innerView.getLayoutParams();
+                // innerLp.width = ViewGroup.LayoutParams.MATCH_PARENT;
+                // innerLp.height = ViewGroup.LayoutParams.MATCH_PARENT;
+                // innerView.setLayoutParams(innerLp);
+                // }
+                // ((LiveAudienceActivity) mContext).onVideoHeightChanged(p.height,
+                // mRootHeight);
             }
             }
         }
         }
     }
     }
@@ -337,8 +340,10 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
                 parentWidth = Math.min(p1, p2);
                 parentWidth = Math.min(p1, p2);
                 parentHeight = Math.max(p1, p2);
                 parentHeight = Math.max(p1, p2);
             }
             }
-//            L.e("changeVideoSize", "mVideoWidth----->" + mVideoWidth + "  mVideoHeight------>" + mVideoHeight);
-//            L.e("changeVideoSize", "parentWidth----->" + parentWidth + "  parentHeight------>" + parentHeight);
+            // L.e("changeVideoSize", "mVideoWidth----->" + mVideoWidth + "
+            // mVideoHeight------>" + mVideoHeight);
+            // L.e("changeVideoSize", "parentWidth----->" + parentWidth + "
+            // parentHeight------>" + parentHeight);
             float parentRatio = parentWidth / parentHeight;
             float parentRatio = parentWidth / parentHeight;
             if (videoRatio != parentRatio) {
             if (videoRatio != parentRatio) {
                 FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mLiveView.getLayoutParams();
                 FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mLiveView.getLayoutParams();
@@ -352,14 +357,15 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
                     p.gravity = Gravity.CENTER;
                     p.gravity = Gravity.CENTER;
                 }
                 }
                 mLiveView.requestLayout();
                 mLiveView.requestLayout();
-//                View innerView = mLiveView.getVideoView();
-//                if (innerView != null) {
-//                    ViewGroup.LayoutParams innerLp = innerView.getLayoutParams();
-//                    innerLp.width = ViewGroup.LayoutParams.MATCH_PARENT;
-//                    innerLp.height = ViewGroup.LayoutParams.MATCH_PARENT;
-//                    innerView.setLayoutParams(innerLp);
-//                }
-//                ((LiveAudienceActivity) mContext).onVideoHeightChanged(p.height, mRootHeight);
+                // View innerView = mLiveView.getVideoView();
+                // if (innerView != null) {
+                // ViewGroup.LayoutParams innerLp = innerView.getLayoutParams();
+                // innerLp.width = ViewGroup.LayoutParams.MATCH_PARENT;
+                // innerLp.height = ViewGroup.LayoutParams.MATCH_PARENT;
+                // innerView.setLayoutParams(innerLp);
+                // }
+                // ((LiveAudienceActivity) mContext).onVideoHeightChanged(p.height,
+                // mRootHeight);
             }
             }
         }
         }
     }
     }
@@ -391,13 +397,12 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
      * 循环播放
      * 循环播放
      */
      */
     private void replay() {
     private void replay() {
-//        if (mVodPlayer != null) {
-//            mVodPlayer.seek(0);
-//            mVodPlayer.resume();
-//        }
+        // if (mVodPlayer != null) {
+        // mVodPlayer.seek(0);
+        // mVodPlayer.resume();
+        // }
     }
     }
 
 
-
     /**
     /**
      * 暂停播放
      * 暂停播放
      */
      */
@@ -441,7 +446,6 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
         }
         }
     }
     }
 
 
-
     @Override
     @Override
     public void stopPlay() {
     public void stopPlay() {
         mChangeToLeft = false;
         mChangeToLeft = false;
@@ -483,19 +487,22 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
         L.e(TAG, "release------->");
         L.e(TAG, "release------->");
     }
     }
 
 
-
     @Override
     @Override
     public ViewGroup getSmallContainer() {
     public ViewGroup getSmallContainer() {
         return mSmallContainer;
         return mSmallContainer;
     }
     }
 
 
-
     @Override
     @Override
     public ViewGroup getRightContainer() {
     public ViewGroup getRightContainer() {
         return mRightContainer;
         return mRightContainer;
     }
     }
 
 
     @Override
     @Override
+    public ViewGroup getBottomRightContainer() {
+        return mBottomRightContainer;
+    }
+
+    @Override
     public ViewGroup getPkContainer() {
     public ViewGroup getPkContainer() {
         return mPkContainer;
         return mPkContainer;
     }
     }
@@ -641,4 +648,16 @@ public class LivePlayTxViewHolder extends LiveRoomPlayViewHolder {
         }, delayTime);
         }, delayTime);
 
 
     }
     }
+
+    @Override
+    public void changeToMultiLinkMic() {
+//        mChangeToLeft = true;
+//        if (mLiveView != null) {
+//            FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mLiveView.getLayoutParams();
+//            params.width = mLiveView.getWidth() / 2;
+//            params.height = DpUtil.dp2px(250);
+//            params.topMargin = DpUtil.dp2px(130);
+//            mLiveView.setLayoutParams(params);
+//        }
+    }
 }
 }

+ 73 - 2
ybvideoandroid/live/src/main/java/com/yunbao/live/views/LivePushTxViewHolder.java

@@ -8,6 +8,7 @@ import android.graphics.BitmapFactory;
 import android.os.Bundle;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.text.TextUtils;
 import android.util.TypedValue;
 import android.util.TypedValue;
+import android.view.Gravity;
 import android.view.TextureView;
 import android.view.TextureView;
 import android.view.ViewGroup;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.FrameLayout;
@@ -41,6 +42,8 @@ import com.yunbao.live.http.LiveHttpConsts;
 import com.yunbao.live.http.LiveHttpUtil;
 import com.yunbao.live.http.LiveHttpUtil;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 
 /**
 /**
  * Created by cxf on 2018/10/7.
  * Created by cxf on 2018/10/7.
@@ -264,6 +267,20 @@ public class LivePushTxViewHolder extends AbsLivePushViewHolder {
         }
         }
     }
     }
 
 
+    @Override
+    public void changeToMultiLinkMic() {
+        // 基于现有setAnchorLinkMic方法的逻辑
+//        if (mPreView == null) {
+//            return;
+//        }
+//
+//        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mPreView.getLayoutParams();
+//        params.width = mPreView.getWidth() / 2;
+//        params.height = DpUtil.dp2px(250);
+//        params.topMargin = DpUtil.dp2px(130);
+//        mPreView.setLayoutParams(params);
+    }
+
     /**
     /**
      * 切换镜像
      * 切换镜像
      */
      */
@@ -440,10 +457,14 @@ public class LivePushTxViewHolder extends AbsLivePushViewHolder {
      * @param touid       连麦者的uid
      * @param touid       连麦者的uid
      * @param toStream    连麦者的stream
      * @param toStream    连麦者的stream
      */
      */
-    public void onLinkMicTxMixStreamEvent(int linkMicType, String touid, String toStream) {
+    public void onLinkMicTxMixStreamEvent(int linkMicType, String touid, String toStream, List<Map<String, String>> members) {
         if (linkMicType == Constants.LINK_MIC_TYPE_NORMAL) {
         if (linkMicType == Constants.LINK_MIC_TYPE_NORMAL) {
             mixStreamWithLinkMicUser(touid, toStream);
             mixStreamWithLinkMicUser(touid, toStream);
         } else {
         } else {
+            if (members != null && members.size() > 0) {
+                mixMultiStreamWithAnchor(members);
+                return;
+            }
             mixStreamWithAnchor(touid, toStream);
             mixStreamWithAnchor(touid, toStream);
         }
         }
     }
     }
@@ -566,5 +587,55 @@ public class LivePushTxViewHolder extends AbsLivePushViewHolder {
 
 
     }
     }
 
 
-
+    private void mixMultiStreamWithAnchor(List<Map<String, String>> members) {
+        if (mLivePusher == null) {
+            return;
+        }
+        V2TXLiveDef.V2TXLiveTranscodingConfig config = new V2TXLiveDef.V2TXLiveTranscodingConfig();
+        config.videoWidth = 540;
+        config.videoHeight = 720;
+        config.videoBitrate = 1500;
+        config.videoFramerate = 15;
+        config.videoGOP = 2;
+        config.audioSampleRate = 48000;
+        config.audioBitrate = 64;
+        config.audioChannels = 2;
+        config.mixStreams = new ArrayList<>();
+        // 主播摄像头的画面位置
+        V2TXLiveDef.V2TXLiveMixStream local = new V2TXLiveDef.V2TXLiveMixStream();
+        local.userId = CommonAppConfig.getInstance().getUid();
+        local.streamId = null; // 本地画面不用填写 streamID,远程需要
+        local.x = 0;
+        local.y = 0;
+        local.width = 270;
+        local.height = 360;
+        local.zOrder = 0;   // zOrder 为 0 代表主播画面位于最底层
+        config.mixStreams.add(local);
+        L.e("混流成员--->" + members.toString());
+        if (members != null && members.size() > 0) {
+            for (int i = 0; i < members.size(); i++) {
+                Map<String, String> member = members.get(i);
+                String userId = member.get("uid");
+                String streamId = member.get("stream");
+                if (!TextUtils.isEmpty(userId) && !TextUtils.isEmpty(streamId)) {
+                    //连麦者的画面位置
+                    int cols = 2;  // 每行2列
+                    int x = (i % cols) * 270;
+                    int y = (i / cols) * 360;
+                    L.e("连麦者---->" + userId + "----stream---->" + streamId + "布局坐标---->" + x + "---->" + y + "---->");
+                    V2TXLiveDef.V2TXLiveMixStream remoteA = new V2TXLiveDef.V2TXLiveMixStream();
+                    remoteA.userId = userId;
+                    remoteA.streamId = streamId; // 本地画面不用填写 streamID,远程需要
+                    remoteA.x = x; //仅供参考
+                    remoteA.y = y; //仅供参考
+                    remoteA.width = 270; //仅供参考
+                    remoteA.height = 360; //仅供参考
+                    remoteA.zOrder = 1;
+                    config.mixStreams.add(remoteA);
+                }
+            }
+        }
+        // 发起云端混流
+        mLivePusher.setMixTranscodingConfig(config);
+    }
 }
 }

+ 26 - 0
ybvideoandroid/live/src/main/res/drawable/selector_exit_link_mic_btn.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- 按压状态 -->
+    <item android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="15dp" />
+            <stroke
+                android:width="1dp"
+                android:color="@color/white" />
+            <solid android:color="#A0000000" />
+        </shape>
+    </item>
+
+    <!-- 正常状态 -->
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="15dp" />
+            <stroke
+                android:width="1dp"
+                android:color="@color/white" />
+            <solid android:color="#80000000" />
+        </shape>
+    </item>
+
+</selector>

+ 20 - 1
ybvideoandroid/live/src/main/res/layout/view_live_anchor.xml

@@ -74,6 +74,23 @@
             android:padding="5dp"
             android:padding="5dp"
             android:src="@mipmap/icon_live_anchor_shop" />
             android:src="@mipmap/icon_live_anchor_shop" />
 
 
+        <TextView
+            android:id="@+id/btn_exit_link_mic"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toLeftOf="@id/btn_pk"
+            android:layout_marginRight="5dp"
+            android:layout_centerVertical="true"
+            android:background="@drawable/selector_exit_link_mic_btn"
+            android:paddingLeft="12dp"
+            android:paddingTop="8dp"
+            android:paddingRight="12dp"
+            android:paddingBottom="8dp"
+            android:text="退出连麦"
+            android:textColor="@color/white"
+            android:textSize="12sp"
+            android:visibility="invisible" />
+
         <ImageView
         <ImageView
             android:id="@+id/btn_pk"
             android:id="@+id/btn_pk"
             android:layout_width="80dp"
             android:layout_width="80dp"
@@ -81,7 +98,9 @@
             android:layout_toLeftOf="@id/btn_function"
             android:layout_toLeftOf="@id/btn_function"
             android:padding="6dp"
             android:padding="6dp"
             android:src="@mipmap/icon_live_pk"
             android:src="@mipmap/icon_live_pk"
-            android:visibility="invisible" />
+            android:visibility="invisible"
+             />
+
 
 
         <ImageView
         <ImageView
             android:id="@+id/btn_chat"
             android:id="@+id/btn_chat"

+ 28 - 15
ybvideoandroid/live/src/main/res/layout/view_live_play_tx.xml

@@ -21,30 +21,43 @@
         />
         />
 
 
 
 
-    <FrameLayout
+    <LinearLayout
         android:id="@+id/pk_container"
         android:id="@+id/pk_container"
         android:layout_width="match_parent"
         android:layout_width="match_parent"
-        android:layout_height="270dp"
+        android:layout_height="540dp"
         android:layout_marginTop="130dp"
         android:layout_marginTop="130dp"
+        android:orientation="vertical"
         >
         >
 
 
-        <com.yunbao.live.custom.MyFrameLayout4
-            android:id="@+id/left_container"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_marginBottom="20dp"
-            />
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="250dp"
+            android:orientation="horizontal">
+
+            <com.yunbao.live.custom.MyFrameLayout4
+                android:id="@+id/left_container"
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                />
+
+            <com.yunbao.live.custom.MyFrameLayout4
+                android:id="@+id/right_container"
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                />
+
+        </LinearLayout>
 
 
         <com.yunbao.live.custom.MyFrameLayout4
         <com.yunbao.live.custom.MyFrameLayout4
-            android:id="@+id/right_container"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_gravity="right"
-            android:layout_marginBottom="20dp"
+            android:id="@+id/bottom_right_container"
+            android:layout_width="match_parent"
+            android:layout_height="250dp"
+            android:layout_gravity="center_horizontal"
             />
             />
 
 
-    </FrameLayout>
-
+    </LinearLayout>
 
 
     <com.yunbao.live.custom.MyFrameLayout3
     <com.yunbao.live.custom.MyFrameLayout3
         android:id="@+id/small_container"
         android:id="@+id/small_container"

+ 53 - 14
ybvideoandroid/live/src/main/res/layout/view_live_push_tx.xml

@@ -18,29 +18,68 @@
             />
             />
     </FrameLayout>
     </FrameLayout>
 
 
-    <FrameLayout
+<!--    <FrameLayout-->
+<!--        android:id="@+id/pk_container"-->
+<!--        android:layout_width="match_parent"-->
+<!--        android:layout_height="270dp"-->
+<!--        android:layout_marginTop="130dp"-->
+<!--        >-->
+
+<!--        <com.yunbao.live.custom.MyFrameLayout4-->
+<!--            android:id="@+id/left_container"-->
+<!--            android:layout_width="0dp"-->
+<!--            android:layout_height="match_parent"-->
+<!--            android:layout_marginBottom="20dp"-->
+<!--            />-->
+
+<!--        <com.yunbao.live.custom.MyFrameLayout4-->
+<!--            android:id="@+id/right_container"-->
+<!--            android:layout_width="0dp"-->
+<!--            android:layout_height="match_parent"-->
+<!--            android:layout_gravity="right"-->
+<!--            android:layout_marginBottom="20dp"-->
+<!--            />-->
+
+<!--    </FrameLayout>-->
+
+    <LinearLayout
         android:id="@+id/pk_container"
         android:id="@+id/pk_container"
         android:layout_width="match_parent"
         android:layout_width="match_parent"
-        android:layout_height="270dp"
+        android:layout_height="500dp"
         android:layout_marginTop="130dp"
         android:layout_marginTop="130dp"
+        android:orientation="vertical"
         >
         >
 
 
-        <com.yunbao.live.custom.MyFrameLayout4
-            android:id="@+id/left_container"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_marginBottom="20dp"
-            />
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="250dp"
+            android:orientation="horizontal">
+
+            <com.yunbao.live.custom.MyFrameLayout4
+                android:id="@+id/left_container"
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                />
+
+            <com.yunbao.live.custom.MyFrameLayout4
+                android:id="@+id/right_container"
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                />
+
+        </LinearLayout>
 
 
         <com.yunbao.live.custom.MyFrameLayout4
         <com.yunbao.live.custom.MyFrameLayout4
-            android:id="@+id/right_container"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_gravity="right"
-            android:layout_marginBottom="20dp"
+            android:id="@+id/bottom_right_container"
+            android:layout_width="match_parent"
+            android:layout_gravity="center_horizontal"
+            android:layout_height="250dp"
             />
             />
 
 
-    </FrameLayout>
+    </LinearLayout>
+
 
 
 
 
     <com.yunbao.live.custom.MyFrameLayout3
     <com.yunbao.live.custom.MyFrameLayout3