集成完直播后提交代码

This commit is contained in:
xuhuixiang
2026-02-06 14:55:21 +08:00
commit ea9ffa06ff
960 changed files with 75063 additions and 0 deletions

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,51 @@
plugins {
id 'com.android.library'
}
android {
compileSdkVersion androidCompileSdkVersion
buildToolsVersion androidBuildToolsVersion
defaultConfig {
minSdkVersion androidMinSdkVersion
targetSdkVersion androidTargetSdkVersion
versionCode 1
versionName "1.0"
consumerProguardFiles "consumer-rules.pro"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: 'interactiveLive']
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation externalAndroidDesign
implementation externalSimpleZXing
implementation externalARouter
annotationProcessor externalARouterCompiler
// Add a downgraded version of the player sdk for the live project single build.
if ("true".equalsIgnoreCase(allInOne)) {
implementation externalPlayerFull
} else {
implementation externalPlayerFullDowngrade
}
implementation project(':LiveInteractive:live_interactive_common')
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.alivc.live.interactive_live">
<application>
<activity
android:name=".InteractLiveActivity"
android:alwaysRetainTaskState="true"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<activity
android:name=".MultiInteractLiveActivity"
android:alwaysRetainTaskState="true"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
</application>
</manifest>

View File

@@ -0,0 +1,199 @@
package com.alivc.live.interactive_live;
import android.content.Context;
import android.text.TextUtils;
import android.widget.FrameLayout;
import com.alivc.live.commonbiz.LocalStreamReader;
import com.alivc.live.commonbiz.ResourcesConst;
import com.alivc.live.commonbiz.test.AliLiveStreamURLUtil;
import com.alivc.live.interactive_common.InteractiveMode;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.interactive_common.listener.InteractLivePushPullListener;
import com.alivc.live.interactive_common.utils.LivePushGlobalConfig;
import com.alivc.live.pusher.AlivcResolutionEnum;
import java.io.File;
import java.util.HashMap;
/**
* 以主播身份进入连麦互动界面的 Controller
*/
public class AnchorController {
private final InteractLiveManager mInteractLiveManager;
private final Context mContext;
private final LocalStreamReader mLocalStreamReader;
//主播预览 View
private FrameLayout mAnchorRenderView;
//观众连麦预览 View
private FrameLayout mViewerRenderView;
// 主播信息
private InteractiveUserData mAnchorUserData;
// 连麦观众信息
private InteractiveUserData mAudienceUserData;
public AnchorController(Context context, InteractiveUserData userData) {
mAnchorUserData = userData;
this.mContext = context;
AlivcResolutionEnum resolution = LivePushGlobalConfig.mAlivcLivePushConfig.getResolution();
int width = AlivcResolutionEnum.getResolutionWidth(resolution, LivePushGlobalConfig.mAlivcLivePushConfig.getLivePushMode());
int height = AlivcResolutionEnum.getResolutionHeight(resolution, LivePushGlobalConfig.mAlivcLivePushConfig.getLivePushMode());
mLocalStreamReader = new LocalStreamReader.Builder()
.setVideoWith(width)
.setVideoHeight(height)
.setVideoStride(width)
.setVideoSize(height * width * 3 / 2)
.setVideoRotation(0)
.setAudioSampleRate(44100)
.setAudioChannel(1)
.setAudioBufferSize(2048)
.build();
// 1v1连麦场景下如果开启了1080P相机采集同时设置回调低分辨率texture
boolean useResolution1080P = LivePushGlobalConfig.mAlivcLivePushConfig.getResolution() == AlivcResolutionEnum.RESOLUTION_1080P;
if (useResolution1080P) {
HashMap<String, String> extras = new HashMap<>();
extras.put("user_specified_observer_texture_low_resolution", "TRUE");
LivePushGlobalConfig.mAlivcLivePushConfig.setExtras(extras);
}
mInteractLiveManager = new InteractLiveManager();
mInteractLiveManager.init(context, InteractiveMode.INTERACTIVE);
// 1v1连麦场景下如果开启了1080P相机采集同时设置回调低分辨率texture
if (useResolution1080P) {
mInteractLiveManager.changeResolution(AlivcResolutionEnum.RESOLUTION_540P);
}
}
/**
* 设置主播预览 View
*
* @param frameLayout 主播预览 View
*/
public void setAnchorRenderView(FrameLayout frameLayout) {
this.mAnchorRenderView = frameLayout;
}
/**
* 设置观众预览 View
*
* @param frameLayout 观众预览 View
*/
public void setViewerRenderView(FrameLayout frameLayout) {
this.mViewerRenderView = frameLayout;
}
/**
* 开始直播
*/
public void startPush() {
externAV();
mInteractLiveManager.startPreviewAndPush(mAnchorUserData, mAnchorRenderView, true);
}
private void externAV() {
if (LivePushGlobalConfig.mAlivcLivePushConfig.isExternMainStream()) {
File yuvFile = ResourcesConst.localCaptureYUVFilePath(mContext);
mLocalStreamReader.readYUVData(yuvFile, (buffer, pts, videoWidth, videoHeight, videoStride, videoSize, videoRotation) -> {
mInteractLiveManager.inputStreamVideoData(buffer, videoWidth, videoHeight, videoStride, videoSize, pts, videoRotation);
});
File pcmFile = ResourcesConst.localCapturePCMFilePath(mContext);
mLocalStreamReader.readPCMData(pcmFile, (buffer, length, pts, audioSampleRate, audioChannel) -> {
mInteractLiveManager.inputStreamAudioData(buffer, length, audioSampleRate, audioChannel, pts);
});
}
}
/**
* 开始连麦
*
* @param userData 要连麦的观众信息
*/
public void startConnect(InteractiveUserData userData) {
if (userData == null || TextUtils.isEmpty(userData.channelId) || TextUtils.isEmpty(userData.userId)) {
return;
}
mAudienceUserData = userData;
mInteractLiveManager.setPullView(userData, mViewerRenderView, false);
mInteractLiveManager.startPullRTCStream(userData);
mInteractLiveManager.setLiveMixTranscodingConfig(mAnchorUserData, userData);
}
/**
* 结束连麦
*/
public void stopConnect() {
mInteractLiveManager.stopPullRTCStream(mAudienceUserData);
mInteractLiveManager.clearLiveMixTranscodingConfig();
}
/**
* 主播是否正在连麦
*
* @return true:正在连麦 false:没有连麦
*/
public boolean isOnConnected() {
return mInteractLiveManager.isPulling(mAudienceUserData);
}
public void resume() {
mInteractLiveManager.resumePush();
mInteractLiveManager.resumePlayRTCStream(mAudienceUserData);
}
public void pause() {
mInteractLiveManager.pausePush();
mInteractLiveManager.pausePlayRTCStream(mAudienceUserData);
}
public void release() {
mInteractLiveManager.release();
mLocalStreamReader.stopYUV();
mLocalStreamReader.stopPcm();
}
public void pauseVideoPlaying() {
mInteractLiveManager.pausePlayRTCStream(mAudienceUserData);
}
public void resumeVideoPlaying() {
mInteractLiveManager.resumePlayRTCStream(mAudienceUserData);
}
public void setInteractLivePushPullListener(InteractLivePushPullListener listener) {
mInteractLiveManager.setInteractLivePushPullListener(listener);
}
public void switchCamera() {
mInteractLiveManager.switchCamera();
}
public void enableSpeakerPhone(boolean enable) {
mInteractLiveManager.enableSpeakerPhone(enable);
}
public void setMute(boolean b) {
mInteractLiveManager.setMute(b);
}
public void enableAudioCapture(boolean enable) {
mInteractLiveManager.enableAudioCapture(enable);
}
public void muteLocalCamera(boolean muteLocalCamera) {
mInteractLiveManager.muteLocalCamera(muteLocalCamera);
}
public void enableLocalCamera(boolean enable) {
mInteractLiveManager.enableLocalCamera(enable);
}
public void sendSEI(String text, int payload) {
mInteractLiveManager.sendCustomMessage(text, payload);
}
}

View File

@@ -0,0 +1,630 @@
package com.alivc.live.interactive_live;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.acker.simplezxing.activity.CaptureActivity;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alivc.live.commonbiz.test.AliLiveStreamURLUtil;
import com.alivc.live.commonui.messageview.AutoScrollMessagesView;
import com.alivc.live.commonui.widgets.LivePushTextSwitch;
import com.alivc.live.commonui.seiview.LivePusherSEIView;
import com.alivc.live.commonui.utils.StatusBarUtil;
import com.alivc.live.commonutils.ToastUtils;
import com.alivc.live.interactive_common.InteractiveConstants;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.interactive_common.listener.ConnectionLostListener;
import com.alivc.live.interactive_common.listener.InteractLivePushPullListener;
import com.alivc.live.interactive_common.listener.InteractLiveTipsViewListener;
import com.alivc.live.interactive_common.utils.InteractLiveIntent;
import com.alivc.live.interactive_common.utils.LivePushGlobalConfig;
import com.alivc.live.commonui.avdialog.AUILiveDialog;
import com.alivc.live.interactive_common.widget.ConnectionLostTipsView;
import com.alivc.live.interactive_common.widget.InteractiveCommonInputView;
import com.alivc.live.interactive_common.widget.InteractiveConnectView;
import com.alivc.live.interactive_common.widget.InteractiveRoomControlView;
import com.alivc.live.interactive_common.widget.RoomAndUserInfoView;
import com.alivc.live.player.annotations.AlivcLivePlayError;
import com.aliyunsdk.queen.menu.QueenBeautyMenu;
import com.aliyunsdk.queen.menu.QueenMenuPanel;
import java.nio.charset.StandardCharsets;
@Route(path = "/interactiveLive/interactLive")
public class InteractLiveActivity extends AppCompatActivity {
private static final int REQ_CODE_PERMISSION = 0x1111;
private AUILiveDialog mAUILiveDialog;
private InteractiveUserData mInteractiveUserData;
//是否是主播端
private boolean mIsAnchor = true;
private String mAnchorId;
//Dialog 弹窗的意图
private InteractLiveIntent mCurrentIntent;
private ImageView mCloseImageView;
private TextView mShowConnectIdTextView;
//大窗口
private FrameLayout mBigFrameLayout;
//小窗口
private FrameLayout mSmallFrameLayout;
private SurfaceView mBigSurfaceView;
private AnchorController mAnchorController;
private ViewerController mViewerController;
private ConnectionLostTipsView mConnectionLostTipsView;
private RoomAndUserInfoView mAnchorInfoView;
private RoomAndUserInfoView mAudienceInfoView;
private InteractiveCommonInputView commonInputView;
private InteractiveConnectView mInteractiveConnectView;
private InteractiveRoomControlView mInteractiveRoomControlView;
private ImageView mBeautyImageView;
// 美颜menu
private QueenMenuPanel mBeautyMenuPanel;
private QueenBeautyMenu mQueenBeautyMenu;
private LivePusherSEIView mSeiView;
private AutoScrollMessagesView mSeiMessageView;
private LivePushTextSwitch mShowCustomMessageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
StatusBarUtil.translucent(this, Color.TRANSPARENT);
setContentView(R.layout.activity_interact_live);
mIsAnchor = getIntent().getBooleanExtra(InteractiveConstants.DATA_TYPE_IS_ANCHOR, true);
InteractiveUserData interactiveUserData = (InteractiveUserData) getIntent().getSerializableExtra(InteractiveConstants.DATA_TYPE_INTERACTIVE_USER_DATA);
mInteractiveUserData = interactiveUserData;
if (mIsAnchor) {
mAnchorController = new AnchorController(this, interactiveUserData);
} else {
mViewerController = new ViewerController(this, interactiveUserData);
}
initView();
initListener();
initData();
}
private void initData() {
if (mIsAnchor) {
mAnchorController.setAnchorRenderView(mBigFrameLayout);
mAnchorController.setViewerRenderView(mSmallFrameLayout);
mAnchorController.startPush();
mAnchorInfoView.setUserData(mInteractiveUserData);
} else {
mViewerController.setAnchorRenderView(mBigFrameLayout);
mViewerController.setViewerRenderView(mSmallFrameLayout);
mViewerController.setAnchorCDNRenderView(mBigSurfaceView);
changeConnectRenderView(false);
showInteractLiveDialog(getResources().getString(R.string.interact_live_connect_author_tips), true);
}
}
private void initView() {
mAUILiveDialog = new AUILiveDialog(this);
mInteractiveConnectView = findViewById(R.id.connect_view);
mBigSurfaceView = findViewById(R.id.big_surface_view);
mCloseImageView = findViewById(R.id.iv_close);
mShowConnectIdTextView = findViewById(R.id.tv_show_connect);
mBigFrameLayout = findViewById(R.id.big_fl);
mSmallFrameLayout = findViewById(R.id.small_fl);
mInteractiveRoomControlView = findViewById(R.id.interactive_setting_view);
mBeautyImageView = findViewById(R.id.iv_beauty);
mBeautyMenuPanel = QueenBeautyMenu.getPanel(this);
mBeautyMenuPanel.onHideMenu();
mBeautyMenuPanel.onHideValidFeatures();
mBeautyMenuPanel.onHideCopyright();
mQueenBeautyMenu = findViewById(R.id.beauty_beauty_menuPanel);
mQueenBeautyMenu.addView(mBeautyMenuPanel);
mSeiView = findViewById(R.id.sei_view);
mSeiView.setSendSeiViewListener(new LivePusherSEIView.SendSeiViewListener() {
@Override
public void onSendSeiClick(int payload, String text) {
if (mIsAnchor) {
mAnchorController.sendSEI(text, payload);
} else {
mViewerController.sendSEI(text, payload);
}
}
});
mSeiMessageView = findViewById(R.id.sei_receive_view);
mShowCustomMessageView = findViewById(R.id.btn_show_custom_message);
mShowCustomMessageView.setTextViewText(getString(R.string.sei_send_custom_message_tv));
mShowCustomMessageView.setOnSwitchToggleListener(isChecked -> {
int visibility = isChecked ? View.VISIBLE : View.GONE;
mSeiView.setVisibility(visibility);
});
TextView mHomeIdTextView = findViewById(R.id.tv_home_id);
mHomeIdTextView.setText(mInteractiveUserData != null ? mInteractiveUserData.channelId : "");
mConnectionLostTipsView = new ConnectionLostTipsView(this);
mBigSurfaceView.setZOrderOnTop(true);
mBigSurfaceView.setZOrderMediaOverlay(true);
mAudienceInfoView = findViewById(R.id.audience_info_view);
mAnchorInfoView = findViewById(R.id.anchor_info_view);
mAudienceInfoView.setVisibility(View.VISIBLE);
mAnchorInfoView.setVisibility(View.VISIBLE);
}
private void initListener() {
//美颜
mBeautyImageView.setOnClickListener(view -> {
if (LivePushGlobalConfig.ENABLE_BEAUTY) {
if (mQueenBeautyMenu.getVisibility() == View.VISIBLE) {
mQueenBeautyMenu.setVisibility(View.GONE);
mBeautyMenuPanel.onHideMenu();
} else {
mQueenBeautyMenu.setVisibility(View.VISIBLE);
mBeautyMenuPanel.onShowMenu();
}
}
});
mConnectionLostTipsView.setConnectionLostListener(new ConnectionLostListener() {
@Override
public void onConfirm() {
runOnUiThread(new Runnable() {
@Override
public void run() {
finish();
}
});
}
});
if (mIsAnchor) {
mAnchorController.setInteractLivePushPullListener(new InteractLivePushPullListener() {
@Override
public void onPullSuccess(InteractiveUserData userData) {
super.onPullSuccess(userData);
changeSmallSurfaceViewVisible(true);
updateConnectTextView(true);
}
@Override
public void onPullError(InteractiveUserData userData, AlivcLivePlayError errorType, String errorMsg) {
super.onPullError(userData, errorType, errorMsg);
runOnUiThread(() -> {
changeSmallSurfaceViewVisible(false);
mAnchorController.stopConnect();
updateConnectTextView(false);
ToastUtils.show(getResources().getString(R.string.interact_live_viewer_left));
});
}
@Override
public void onPullStop(InteractiveUserData userData) {
super.onPullStop(userData);
runOnUiThread(() -> {
changeSmallSurfaceViewVisible(false);
updateConnectTextView(false);
});
}
@Override
public void onPushSuccess() {
super.onPushSuccess();
}
@Override
public void onPushError() {
super.onPushError();
}
@Override
public void onVideoEnabled(boolean enable) {
super.onVideoEnabled(enable);
}
@Override
public void onConnectionLost() {
super.onConnectionLost();
runOnUiThread(new Runnable() {
@Override
public void run() {
mConnectionLostTipsView.show();
}
});
}
@Override
public void onReceiveSEIMessage(int payload, byte[] data) {
super.onReceiveSEIMessage(payload, data);
mSeiMessageView.appendMessage("[rtc] payload=" + payload + ", " + new String(data, StandardCharsets.UTF_8));
}
@Override
public void onPlayerSei(int payload, byte[] uuid, byte[] data) {
super.onPlayerSei(payload, uuid, data);
mSeiMessageView.appendMessage("[cdn] payload=" + payload + ", " + new String(data, StandardCharsets.UTF_8));
}
@Override
public void onReceiveSEIDelay(String src, String type, String msg) {
super.onReceiveSEIDelay(src, type, msg);
mSeiMessageView.appendMessage("[" + src + "][" + type + "][" + msg + "ms]");
}
});
} else {
mViewerController.setInteractLivePushPullListener(new InteractLivePushPullListener() {
@Override
public void onPullSuccess(InteractiveUserData userData) {
super.onPullSuccess(userData);
}
@Override
public void onPullError(InteractiveUserData userData, AlivcLivePlayError errorType, String errorMsg) {
super.onPullError(userData, errorType, errorMsg);
runOnUiThread(() -> {
if (errorType == AlivcLivePlayError.AlivcLivePlayErrorStreamStopped) {
finish();
}
});
}
@Override
public void onPullStop(InteractiveUserData userData) {
super.onPullStop(userData);
}
@Override
public void onPushSuccess() {
super.onPushSuccess();
if (mViewerController != null) {
mViewerController.pullOtherStream();
}
runOnUiThread(() -> {
updateConnectTextView(true);
});
}
@Override
public void onPushError() {
super.onPushError();
}
@Override
public void onConnectionLost() {
super.onConnectionLost();
runOnUiThread(new Runnable() {
@Override
public void run() {
mConnectionLostTipsView.show();
}
});
}
@Override
public void onReceiveSEIMessage(int payload, byte[] data) {
super.onReceiveSEIMessage(payload, data);
mSeiMessageView.appendMessage("[rtc] payload=" + payload + ", " + new String(data, StandardCharsets.UTF_8));
}
@Override
public void onPlayerSei(int payload, byte[] uuid, byte[] data) {
super.onPlayerSei(payload, uuid, data);
mSeiMessageView.appendMessage("[cdn] payload=" + payload + ", " + new String(data, StandardCharsets.UTF_8));
}
@Override
public void onReceiveSEIDelay(String src, String type, String msg) {
super.onReceiveSEIDelay(src, type, msg);
mSeiMessageView.appendMessage("[" + src + "][" + type + "][" + msg + "ms]");
}
@Override
public void onVideoEnabled(boolean enable) {
super.onVideoEnabled(enable);
}
});
}
//开始连麦
mInteractiveConnectView.setConnectClickListener(() -> {
if (mIsAnchor) {
if (mAnchorController.isOnConnected()) {
//主播端停止连麦
mCurrentIntent = InteractLiveIntent.INTENT_STOP_PULL;
showInteractLiveDialog(getResources().getString(R.string.interact_live_connect_finish_tips), false);
} else {
//主播端开始连麦,输入用户 id
showInteractLiveDialog(getResources().getString(R.string.interact_live_connect_tips), true);
}
} else {
if (mViewerController.isPushing()) {
//观众端停止连麦
mCurrentIntent = InteractLiveIntent.INTENT_STOP_PUSH;
showInteractLiveDialog(getResources().getString(R.string.interact_live_connect_finish_tips), false);
} else {
//观众端开始连麦
if (mViewerController.hasAnchorId()) {
changeConnectRenderView(true);
mViewerController.startConnect();
changeSmallSurfaceViewVisible(true);
} else {
showInteractLiveDialog(getResources().getString(R.string.interact_live_connect_author_tips), true);
}
}
}
});
mCloseImageView.setOnClickListener(view -> {
mCurrentIntent = InteractLiveIntent.INTENT_FINISH;
showInteractLiveDialog(getResources().getString(R.string.interact_live_leave_room_tips), false);
});
mInteractiveRoomControlView.setOnClickEventListener(new InteractiveRoomControlView.OnClickEventListener() {
@Override
public void onClickSwitchCamera() {
if (mIsAnchor) {
mAnchorController.switchCamera();
} else {
mViewerController.switchCamera();
}
}
@Override
public void onClickSpeakerPhone(boolean enable) {
if (mIsAnchor) {
mAnchorController.enableSpeakerPhone(enable);
} else {
mViewerController.enableSpeakerPhone(enable);
}
}
@Override
public void onClickMuteAudio(boolean mute) {
if (mIsAnchor) {
mAnchorController.setMute(mute);
} else {
mViewerController.setMute(mute);
}
}
@Override
public void onClickMuteVideo(boolean mute) {
if (mIsAnchor) {
mAnchorController.muteLocalCamera(mute);
} else {
mViewerController.muteLocalCamera(mute);
}
}
@Override
public void onClickEnableAudio(boolean enable) {
if (mIsAnchor) {
mAnchorController.enableAudioCapture(enable);
} else {
mViewerController.enableAudioCapture(enable);
}
}
@Override
public void onClickEnableVideo(boolean enable) {
if (mIsAnchor) {
mAnchorController.enableLocalCamera(enable);
} else {
mViewerController.enableLocalCamera(enable);
}
}
});
}
private void changeSmallSurfaceViewVisible(boolean isShowSurfaceView) {
mSmallFrameLayout.setVisibility(isShowSurfaceView ? View.VISIBLE : View.INVISIBLE);
mInteractiveConnectView.isShow(isShowSurfaceView);
}
public void updateConnectTextView(boolean connecting) {
if (connecting) {
mShowConnectIdTextView.setVisibility(View.VISIBLE);
mInteractiveConnectView.connected();
} else {
mShowConnectIdTextView.setVisibility(View.GONE);
mInteractiveConnectView.unConnected();
}
}
private void showInteractLiveDialog(String content, boolean showInputView, boolean showQR) {
commonInputView = new InteractiveCommonInputView(InteractLiveActivity.this);
commonInputView.setViewType(InteractiveCommonInputView.ViewType.INTERACTIVE);
commonInputView.showInputView(content, showInputView);
mAUILiveDialog.setContentView(commonInputView);
mAUILiveDialog.show();
commonInputView.setOnInteractLiveTipsViewListener(new InteractLiveTipsViewListener() {
@Override
public void onCancel() {
if (mAUILiveDialog.isShowing()) {
mAUILiveDialog.dismiss();
}
}
@Override
public void onConfirm() {
if (mCurrentIntent == InteractLiveIntent.INTENT_STOP_PULL && mIsAnchor) {
//主播结束连麦
mAUILiveDialog.dismiss();
mAnchorController.stopConnect();
updateConnectTextView(false);
changeSmallSurfaceViewVisible(false);
} else if (mCurrentIntent == InteractLiveIntent.INTENT_STOP_PUSH && !mIsAnchor) {
//观众结束连麦
mAUILiveDialog.dismiss();
mViewerController.stopConnect();
changeConnectRenderView(false);
updateConnectTextView(false);
changeSmallSurfaceViewVisible(false);
} else if (mCurrentIntent == InteractLiveIntent.INTENT_FINISH) {
finish();
}
}
@Override
public void onInputConfirm(InteractiveUserData userData) {
hideInputSoftFromWindowMethod(InteractLiveActivity.this, commonInputView);
if (TextUtils.isEmpty(userData.userId)) {
ToastUtils.show(getResources().getString(R.string.interact_live_connect_input_error_tips));
return;
}
userData.channelId = mInteractiveUserData != null ? mInteractiveUserData.channelId : "";
userData.url = AliLiveStreamURLUtil.generateInteractivePullUrl(userData.channelId, userData.userId);
mAUILiveDialog.dismiss();
if (mIsAnchor) {
//主播端,输入观众 id 后,开始连麦
mAnchorController.startConnect(userData);
} else {
//观众端,输入主播 id 后,观看直播
mViewerController.updateAnchorUserData(userData);
mViewerController.watchLive();
setInfoView(mInteractiveUserData != null ? mInteractiveUserData.channelId : "", userData.userId,
mInteractiveUserData != null ? mInteractiveUserData.userId : "");
}
}
@Override
public void onQrClick() {
startQr();
}
});
}
private void showInteractLiveDialog(String content, boolean showInputView) {
showInteractLiveDialog(content, showInputView, false);
}
private void changeConnectRenderView(boolean connect) {
mBigFrameLayout.setVisibility(connect ? View.VISIBLE : View.GONE);
mBigSurfaceView.setVisibility(connect ? View.GONE : View.VISIBLE);
}
@Override
protected void onResume() {
super.onResume();
if (mIsAnchor) {
mAnchorController.resume();
} else {
mViewerController.resume();
}
}
@Override
protected void onPause() {
super.onPause();
if (mIsAnchor) {
mAnchorController.pause();
} else {
mViewerController.pause();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mIsAnchor) {
mAnchorController.release();
} else {
mViewerController.release();
}
}
public void hideInputSoftFromWindowMethod(Context context, View view) {
try {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
} catch (Exception e) {
e.printStackTrace();
}
}
private void setInfoView(String roomId, String anchorId, String audienceId) {
mAudienceInfoView.setUserInfo(roomId, audienceId);
mAnchorInfoView.setUserInfo(roomId, anchorId);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case CaptureActivity.REQ_CODE:
switch (resultCode) {
case RESULT_OK:
if (commonInputView != null) {
commonInputView.setQrResult(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
break;
case RESULT_CANCELED:
if (data != null && commonInputView != null) {
// for some reason camera is not working correctly
commonInputView.setQrResult(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
break;
default:
break;
}
break;
default:
break;
}
}
private void startQr() {
if (ContextCompat.checkSelfPermission(InteractLiveActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// Do not have the permission of camera, request it.
ActivityCompat.requestPermissions(InteractLiveActivity.this, new String[]{Manifest.permission.CAMERA}, REQ_CODE_PERMISSION);
} else {
// Have gotten the permission
startCaptureActivityForResult();
}
}
private void startCaptureActivityForResult() {
Intent intent = new Intent(InteractLiveActivity.this, CaptureActivity.class);
Bundle bundle = new Bundle();
bundle.putBoolean(CaptureActivity.KEY_NEED_BEEP, CaptureActivity.VALUE_BEEP);
bundle.putBoolean(CaptureActivity.KEY_NEED_VIBRATION, CaptureActivity.VALUE_VIBRATION);
bundle.putBoolean(CaptureActivity.KEY_NEED_EXPOSURE, CaptureActivity.VALUE_NO_EXPOSURE);
bundle.putByte(CaptureActivity.KEY_FLASHLIGHT_MODE, CaptureActivity.VALUE_FLASHLIGHT_OFF);
bundle.putByte(CaptureActivity.KEY_ORIENTATION_MODE, CaptureActivity.VALUE_ORIENTATION_AUTO);
bundle.putBoolean(CaptureActivity.KEY_SCAN_AREA_FULL_SCREEN, CaptureActivity.VALUE_SCAN_AREA_FULL_SCREEN);
bundle.putBoolean(CaptureActivity.KEY_NEED_SCAN_HINT_TEXT, CaptureActivity.VALUE_SCAN_HINT_TEXT);
intent.putExtra(CaptureActivity.EXTRA_SETTING_BUNDLE, bundle);
startActivityForResult(intent, CaptureActivity.REQ_CODE);
}
}

View File

@@ -0,0 +1,272 @@
package com.alivc.live.interactive_live;
import static com.alivc.live.interactive_common.utils.LivePushGlobalConfig.mAlivcLivePushConfig;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;
import android.widget.FrameLayout;
import com.alivc.live.commonbiz.seidelay.SEISourceType;
import com.alivc.live.interactive_common.InteractLiveBaseManager;
import com.alivc.live.interactive_common.InteractiveBaseUtil;
import com.alivc.live.interactive_common.InteractiveMode;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.pusher.AlivcLiveMixStream;
import com.alivc.live.pusher.AlivcLiveTranscodingConfig;
import com.aliyun.player.AliPlayer;
import com.aliyun.player.AliPlayerFactory;
import com.aliyun.player.IPlayer;
import com.aliyun.player.nativeclass.PlayerConfig;
import com.aliyun.player.source.UrlSource;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
public class InteractLiveManager extends InteractLiveBaseManager {
// 连麦场景下普通观众拉取主播的旁路CDN直播流
private AliPlayer mAliPlayer;
@Override
public void init(Context context, InteractiveMode interactiveMode) {
super.init(context, interactiveMode);
initCDNPlayer();
}
@Override
public void release() {
super.release();
stopPullCDNStream();
releaseCDNPlayer();
}
/**
* 连麦场景下设置混流
*/
public void setLiveMixTranscodingConfig(InteractiveUserData anchorUserData, InteractiveUserData audienceUserData) {
if (anchorUserData == null || TextUtils.isEmpty(anchorUserData.channelId) || TextUtils.isEmpty(anchorUserData.userId)) {
clearLiveMixTranscodingConfig();
return;
}
if (audienceUserData == null || TextUtils.isEmpty(audienceUserData.channelId) || TextUtils.isEmpty(audienceUserData.userId)) {
clearLiveMixTranscodingConfig();
return;
}
if (mAlivcLivePushConfig == null) {
return;
}
if (mAlivcLivePusher == null) {
return;
}
ArrayList<AlivcLiveMixStream> mixStreams = new ArrayList<>();
// 添加主播混流窗口
AlivcLiveMixStream anchorMixStream = new AlivcLiveMixStream();
anchorMixStream.userId = anchorUserData.userId;
anchorMixStream.x = 0;
anchorMixStream.y = 0;
anchorMixStream.width = mAlivcLivePushConfig.getWidth();
anchorMixStream.height = mAlivcLivePushConfig.getHeight();
anchorMixStream.zOrder = 1;
anchorMixStream.backgroundImageUrl = "https://alivc-demo-cms.alicdn.com/versionProduct/resources/pictures/siheng.jpg";
mixStreams.add(anchorMixStream);
Log.d(TAG, "anchorMixStream: " + anchorMixStream);
// 添加连麦观众混流窗口
AlivcLiveMixStream audienceMixStream = new AlivcLiveMixStream();
if (mAudienceFrameLayout != null) {
audienceMixStream.userId = audienceUserData.userId;
audienceMixStream.x = (int) mAudienceFrameLayout.getX() / 3;
audienceMixStream.y = (int) mAudienceFrameLayout.getY() / 3;
audienceMixStream.width = mAudienceFrameLayout.getWidth() / 2;
audienceMixStream.height = mAudienceFrameLayout.getHeight() / 2;
audienceMixStream.zOrder = 2;
audienceMixStream.mixSourceType = InteractiveBaseUtil.covertVideoStreamType2MixSourceType(audienceUserData.videoStreamType);
audienceMixStream.backgroundImageUrl = "https://alivc-demo-cms.alicdn.com/versionProduct/resources/pictures/lantu.jpg";
mixStreams.add(audienceMixStream);
Log.d(TAG, "audienceMixStream: " + audienceMixStream);
}
AlivcLiveTranscodingConfig transcodingConfig = new AlivcLiveTranscodingConfig();
transcodingConfig.mixStreams = mixStreams;
mAlivcLivePusher.setLiveMixTranscodingConfig(transcodingConfig);
}
/**
* 连麦场景添加混流
*/
public void addAnchorMixTranscodingConfig(InteractiveUserData anchorUserData) {
if (anchorUserData == null || TextUtils.isEmpty(anchorUserData.channelId) || TextUtils.isEmpty(anchorUserData.userId)) {
clearLiveMixTranscodingConfig();
return;
}
if (mAlivcLivePushConfig == null) {
return;
}
if (mAlivcLivePusher == null) {
return;
}
AlivcLiveMixStream anchorMixStream = new AlivcLiveMixStream();
anchorMixStream.userId = anchorUserData.userId;
anchorMixStream.x = 0;
anchorMixStream.y = 0;
anchorMixStream.width = mAlivcLivePushConfig.getWidth();
anchorMixStream.height = mAlivcLivePushConfig.getHeight();
anchorMixStream.zOrder = 1;
anchorMixStream.mixSourceType = InteractiveBaseUtil.covertVideoStreamType2MixSourceType(anchorUserData.videoStreamType);
anchorMixStream.backgroundImageUrl = "https://alivc-demo-cms.alicdn.com/versionProduct/resources/pictures/siheng.jpg";
mMultiInteractLiveMixStreamsArray.add(anchorMixStream);
mMixInteractLiveTranscodingConfig.mixStreams = mMultiInteractLiveMixStreamsArray;
mAlivcLivePusher.setLiveMixTranscodingConfig(mMixInteractLiveTranscodingConfig);
}
/**
* 多人连麦场景添加混流
*/
public void addAudienceMixTranscodingConfig(InteractiveUserData audienceUserData, FrameLayout frameLayout) {
if (audienceUserData == null || TextUtils.isEmpty(audienceUserData.channelId) || TextUtils.isEmpty(audienceUserData.userId)) {
return;
}
if (mAlivcLivePusher == null) {
return;
}
AlivcLiveMixStream audienceMixStream = new AlivcLiveMixStream();
audienceMixStream.userId = audienceUserData.userId;
audienceMixStream.x = (int) frameLayout.getX() / 3;
audienceMixStream.y = (int) frameLayout.getY() / 3;
audienceMixStream.width = frameLayout.getWidth() / 3;
audienceMixStream.height = frameLayout.getHeight() / 3;
audienceMixStream.zOrder = 2;
audienceMixStream.mixSourceType = InteractiveBaseUtil.covertVideoStreamType2MixSourceType(audienceUserData.videoStreamType);
audienceMixStream.backgroundImageUrl = "https://alivc-demo-cms.alicdn.com/versionProduct/resources/pictures/yiliang.png";
mMultiInteractLiveMixStreamsArray.add(audienceMixStream);
mMixInteractLiveTranscodingConfig.mixStreams = mMultiInteractLiveMixStreamsArray;
mAlivcLivePusher.setLiveMixTranscodingConfig(mMixInteractLiveTranscodingConfig);
}
/**
* 多人连麦场景移除混流
*/
public void removeAudienceLiveMixTranscodingConfig(InteractiveUserData audienceUserData, String anchorId) {
if (audienceUserData == null || TextUtils.isEmpty(audienceUserData.channelId) || TextUtils.isEmpty(audienceUserData.userId)) {
return;
}
AlivcLiveMixStream mixStream = findMixStreamByUserData(audienceUserData);
if (mixStream == null) {
return;
}
mMultiInteractLiveMixStreamsArray.remove(mixStream);
//Array 中只剩主播 id说明无人连麦
if (mMultiInteractLiveMixStreamsArray.size() == 1 && mMultiInteractLiveMixStreamsArray.get(0).userId.equals(anchorId)) {
clearLiveMixTranscodingConfig();
} else {
mMixInteractLiveTranscodingConfig.mixStreams = mMultiInteractLiveMixStreamsArray;
if (mAlivcLivePusher != null) {
mAlivcLivePusher.setLiveMixTranscodingConfig(mMixInteractLiveTranscodingConfig);
}
}
}
/**
* 初始化基础直播播放器用于播放CDN基础直播流rtmp/http-flv
*/
private void initCDNPlayer() {
mAliPlayer = AliPlayerFactory.createAliPlayer(mContext);
PlayerConfig playerConfig = mAliPlayer.getConfig();
// 纯音频 或 纯视频 的flv 需要设置 以加快起播
// TODO How to enable flv_strict_header
// 起播缓存,越大起播越稳定,但会影响起播时间,可酌情设置
playerConfig.mStartBufferDuration = 1000;
// 卡顿恢复需要的缓存网络不好的情况可以设置大一些当前纯音频设置500还好视频的话建议用默认值3000.
playerConfig.mHighBufferDuration = 500;
// 需要开启SEI监听
playerConfig.mEnableSEI = true;
mAliPlayer.setConfig(playerConfig);
mAliPlayer.setAutoPlay(true);
mAliPlayer.setOnErrorListener(errorInfo -> {
mAliPlayer.prepare();
});
// TODO keria: Remove the SEI function of the player first, as it will cause bytecode conflicts between the player SDK and the push SDK.
// mAliPlayer.setOnSeiDataListener(new IPlayer.OnSeiDataListener() {
// @Override
// public void onSeiData(int type, byte[] uuid, byte[] data) {
// String sei = new String(data, StandardCharsets.UTF_8);
// mSEIDelayManager.receiveSEI(SEISourceType.CDN, sei);
//
// if (mInteractLivePushPullListener != null) {
// mInteractLivePushPullListener.onPlayerSei(type, uuid, data);
// }
// }
// });
}
/**
* 销毁基础直播播放器用于播放CDN基础直播流rtmp/http-flv
*/
private void releaseCDNPlayer() {
if (mAliPlayer != null) {
mAliPlayer.release();
mAliPlayer = null;
}
}
/**
* 连麦场景下,普通观众设置 CDN 拉流时,渲染的 Surface
*
* @param surfaceHolder 播放器渲染画面的 Surface
*/
public void setPullView(SurfaceHolder surfaceHolder) {
if (mAliPlayer != null) {
mAliPlayer.setDisplay(surfaceHolder);
}
}
/**
* 连麦场景下普通观众开始播放主播的旁路CDN直播流
*/
public void startPullCDNStream(String pullUrl) {
if (TextUtils.isEmpty(pullUrl)) {
return;
}
if (mAliPlayer != null) {
UrlSource urlSource = new UrlSource();
urlSource.setUri(pullUrl);
mAliPlayer.setDataSource(urlSource);
mAliPlayer.prepare();
}
}
/**
* 连麦场景下普通观众停止播放主播的旁路CDN直播流
*/
public void stopPullCDNStream() {
if (mAliPlayer != null) {
mAliPlayer.stop();
}
}
}

View File

@@ -0,0 +1,146 @@
package com.alivc.live.interactive_live;
import android.content.Context;
import android.widget.FrameLayout;
import com.alivc.live.commonbiz.LocalStreamReader;
import com.alivc.live.commonbiz.ResourcesConst;
import com.alivc.live.interactive_common.InteractiveMode;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.interactive_common.listener.InteractLivePushPullListener;
import com.alivc.live.interactive_common.utils.LivePushGlobalConfig;
import com.alivc.live.pusher.AlivcResolutionEnum;
import java.io.File;
/**
* 以主播身份进入多人连麦互动界面的 Controller
*/
public class MultiAnchorController {
private final InteractLiveManager mInteractLiveManager;
private final Context mContext;
private final LocalStreamReader mLocalStreamReader;
//主播预览 View
private FrameLayout mAnchorRenderView;
//主播推流地址
private final InteractiveUserData mPushUserData;
public MultiAnchorController(Context context, InteractiveUserData userData) {
this.mContext = context;
AlivcResolutionEnum resolution = LivePushGlobalConfig.mAlivcLivePushConfig.getResolution();
int width = AlivcResolutionEnum.getResolutionWidth(resolution, LivePushGlobalConfig.mAlivcLivePushConfig.getLivePushMode());
int height = AlivcResolutionEnum.getResolutionHeight(resolution, LivePushGlobalConfig.mAlivcLivePushConfig.getLivePushMode());
mLocalStreamReader = new LocalStreamReader.Builder()
.setVideoWith(width)
.setVideoHeight(height)
.setVideoStride(width)
.setVideoSize(height * width * 3 / 2)
.setVideoRotation(0)
.setAudioSampleRate(44100)
.setAudioChannel(1)
.setAudioBufferSize(2048)
.build();
mPushUserData = userData;
mInteractLiveManager = new InteractLiveManager();
mInteractLiveManager.init(context, InteractiveMode.MULTI_INTERACTIVE);
}
/**
* 设置主播预览 View
*
* @param frameLayout 主播预览 View
*/
public void setAnchorRenderView(FrameLayout frameLayout) {
this.mAnchorRenderView = frameLayout;
}
/**
* 开始直播
*/
public void startPush() {
externAV();
mInteractLiveManager.startPreviewAndPush(mPushUserData, mAnchorRenderView, true);
mInteractLiveManager.addAnchorMixTranscodingConfig(mPushUserData);
}
public void startConnect(InteractiveUserData userData, FrameLayout frameLayout) {
if (userData == null) {
return;
}
mInteractLiveManager.setPullView(userData, frameLayout, false);
mInteractLiveManager.startPullRTCStream(userData);
mInteractLiveManager.addAudienceMixTranscodingConfig(userData, frameLayout);
}
public boolean isOnConnected(String key) {
return mInteractLiveManager.isPulling(mInteractLiveManager.getUserDataByKey(key));
}
private void externAV() {
if (LivePushGlobalConfig.mAlivcLivePushConfig.isExternMainStream()) {
File yuvFile = ResourcesConst.localCaptureYUVFilePath(mContext);
mLocalStreamReader.readYUVData(yuvFile, (buffer, pts, videoWidth, videoHeight, videoStride, videoSize, videoRotation) -> {
mInteractLiveManager.inputStreamVideoData(buffer, videoWidth, videoHeight, videoStride, videoSize, pts, videoRotation);
});
File pcmFile = ResourcesConst.localCapturePCMFilePath(mContext);
mLocalStreamReader.readPCMData(pcmFile, (buffer, length, pts, audioSampleRate, audioChannel) -> {
mInteractLiveManager.inputStreamAudioData(buffer, length, audioSampleRate, audioChannel, pts);
});
}
}
/**
* 结束连麦
*/
public void stopConnect(String key) {
InteractiveUserData userData = mInteractLiveManager.getUserDataByKey(key);
mInteractLiveManager.stopPullRTCStream(userData);
mInteractLiveManager.removeAudienceLiveMixTranscodingConfig(userData, mPushUserData != null ? mPushUserData.userId : "");
}
public void resume() {
mInteractLiveManager.resumePush();
mInteractLiveManager.resumePlayRTCStream();
}
public void pause() {
mInteractLiveManager.pausePush();
mInteractLiveManager.pausePlayRTCStream();
}
public void release() {
mInteractLiveManager.release();
mLocalStreamReader.stopYUV();
mLocalStreamReader.stopPcm();
}
public void setMultiInteractLivePushPullListener(InteractLivePushPullListener listener) {
mInteractLiveManager.setInteractLivePushPullListener(listener);
}
public void switchCamera() {
mInteractLiveManager.switchCamera();
}
public void enableSpeakerPhone(boolean enable) {
mInteractLiveManager.enableSpeakerPhone(enable);
}
public void setMute(boolean b) {
mInteractLiveManager.setMute(b);
}
public void enableAudioCapture(boolean enable) {
mInteractLiveManager.enableAudioCapture(enable);
}
public void muteLocalCamera(boolean muteLocalCamera) {
mInteractLiveManager.muteLocalCamera(muteLocalCamera);
}
public void enableLocalCamera(boolean enable) {
mInteractLiveManager.enableLocalCamera(enable);
}
}

View File

@@ -0,0 +1,357 @@
package com.alivc.live.interactive_live;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alivc.live.commonbiz.test.AliLiveStreamURLUtil;
import com.alivc.live.commonui.avdialog.AUILiveDialog;
import com.alivc.live.commonui.messageview.AutoScrollMessagesView;
import com.alivc.live.commonui.utils.StatusBarUtil;
import com.alivc.live.commonutils.ToastUtils;
import com.alivc.live.interactive_common.InteractiveConstants;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.interactive_common.listener.ConnectionLostListener;
import com.alivc.live.interactive_common.listener.InteractLivePushPullListener;
import com.alivc.live.interactive_common.listener.InteractLiveTipsViewListener;
import com.alivc.live.interactive_common.utils.InteractLiveIntent;
import com.alivc.live.interactive_common.widget.ConnectionLostTipsView;
import com.alivc.live.interactive_common.widget.InteractiveCommonInputView;
import com.alivc.live.interactive_common.widget.InteractiveConnectView;
import com.alivc.live.interactive_common.widget.InteractiveRoomControlView;
import com.alivc.live.interactive_common.widget.MultiAlivcLiveView;
import com.alivc.live.player.annotations.AlivcLivePlayError;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Route(path = "/interactiveLive/multiInteractLive")
public class MultiInteractLiveActivity extends AppCompatActivity {
private AUILiveDialog mAUILiveDialog;
//Dialog 弹窗的意图
private InteractLiveIntent mCurrentIntent;
private ImageView mCloseImageView;
private TextView mShowConnectIdTextView;
//大窗口
private FrameLayout mBigFrameLayout;
private SurfaceView mBigSurfaceView;
private TextView mHomeIdTextView;
private InteractiveUserData mAnchorUserData;
private MultiAnchorController mMultiAnchorController;
//根据 TextView 获取其他 View
private final Map<TextView, MultiAlivcLiveView> mViewCombMap = new HashMap<>();
//根据 id 获取其他 View
private final Map<String, MultiAlivcLiveView> mIdViewCombMap = new HashMap<>();
private ConnectionLostTipsView mConnectionLostTipsView;
private InteractiveRoomControlView mInteractiveRoomControlView;
private InteractiveConnectView mInteractiveConnectView1;
private InteractiveConnectView mInteractiveConnectView2;
private InteractiveConnectView mInteractiveConnectView3;
private InteractiveConnectView mInteractiveConnectView4;
private AutoScrollMessagesView mSeiMessageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
StatusBarUtil.translucent(this, Color.TRANSPARENT);
setContentView(R.layout.activity_multi_interact_live);
initView();
initListener();
initData();
}
private void initData() {
InteractiveUserData anchorUserData = (InteractiveUserData) getIntent().getSerializableExtra(InteractiveConstants.DATA_TYPE_INTERACTIVE_USER_DATA);
mAnchorUserData = anchorUserData;
mMultiAnchorController = new MultiAnchorController(this, anchorUserData);
mMultiAnchorController.setMultiInteractLivePushPullListener(new InteractLivePushPullListener() {
@Override
public void onPullSuccess(InteractiveUserData userData) {
super.onPullSuccess(userData);
if (userData == null) {
return;
}
String viewKey = userData.getKey();
MultiAlivcLiveView multiAlivcLiveView = mIdViewCombMap.get(viewKey);
if (multiAlivcLiveView != null) {
changeSmallSurfaceViewVisible(true, multiAlivcLiveView);
updateConnectTextView(true, multiAlivcLiveView.getConnectTextView());
}
}
@Override
public void onPullError(InteractiveUserData userData, AlivcLivePlayError errorType, String errorMsg) {
super.onPullError(userData, errorType, errorMsg);
runOnUiThread(() -> {
if (userData == null) {
return;
}
String viewKey = userData.getKey();
mMultiAnchorController.stopConnect(viewKey);
MultiAlivcLiveView multiAlivcLiveView = mIdViewCombMap.get(viewKey);
if (multiAlivcLiveView != null) {
changeSmallSurfaceViewVisible(false, multiAlivcLiveView);
updateConnectTextView(false, multiAlivcLiveView.getConnectTextView());
}
ToastUtils.show(getResources().getString(R.string.interact_live_viewer_left));
});
}
@Override
public void onConnectionLost() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mConnectionLostTipsView.show();
}
});
}
@Override
public void onReceiveSEIMessage(int payload, byte[] data) {
super.onReceiveSEIMessage(payload, data);
mSeiMessageView.appendMessage("[rtc] payload=" + payload + ", " + new String(data, StandardCharsets.UTF_8));
}
@Override
public void onReceiveSEIDelay(String src, String type, String msg) {
super.onReceiveSEIDelay(src, type, msg);
mSeiMessageView.appendMessage("[" + src + "][" + type + "][" + msg + "ms]");
}
});
mMultiAnchorController.setAnchorRenderView(mBigFrameLayout);
mMultiAnchorController.startPush();
mHomeIdTextView.setText(anchorUserData.channelId);
}
private void initView() {
mAUILiveDialog = new AUILiveDialog(this);
mInteractiveConnectView1 = findViewById(R.id.connect_view_1);
mInteractiveConnectView2 = findViewById(R.id.connect_view_2);
mInteractiveConnectView3 = findViewById(R.id.connect_view_3);
mInteractiveConnectView4 = findViewById(R.id.connect_view_4);
mBigSurfaceView = findViewById(R.id.big_surface_view);
mCloseImageView = findViewById(R.id.iv_close);
mShowConnectIdTextView = findViewById(R.id.tv_show_connect);
mBigFrameLayout = findViewById(R.id.big_fl);
mInteractiveRoomControlView = findViewById(R.id.interactive_setting_view);
mSeiMessageView = findViewById(R.id.sei_receive_view);
//小窗口
FrameLayout mSmallFrameLayout1 = findViewById(R.id.small_fl_1);
FrameLayout mSmallFrameLayout2 = findViewById(R.id.small_fl_2);
FrameLayout mSmallFrameLayout3 = findViewById(R.id.small_fl_3);
FrameLayout mSmallFrameLayout4 = findViewById(R.id.small_fl_4);
mHomeIdTextView = findViewById(R.id.tv_home_id);
mConnectionLostTipsView = new ConnectionLostTipsView(this);
mBigSurfaceView.setZOrderOnTop(true);
mBigSurfaceView.setZOrderMediaOverlay(true);
MultiAlivcLiveView multiAlivcLiveView1 = new MultiAlivcLiveView(mInteractiveConnectView1.getConnectFrameLayout(), mSmallFrameLayout1, mInteractiveConnectView1.getConnectTextView());
MultiAlivcLiveView multiAlivcLiveView2 = new MultiAlivcLiveView(mInteractiveConnectView2.getConnectFrameLayout(), mSmallFrameLayout2, mInteractiveConnectView2.getConnectTextView());
MultiAlivcLiveView multiAlivcLiveView3 = new MultiAlivcLiveView(mInteractiveConnectView3.getConnectFrameLayout(), mSmallFrameLayout3, mInteractiveConnectView3.getConnectTextView());
MultiAlivcLiveView multiAlivcLiveView4 = new MultiAlivcLiveView(mInteractiveConnectView4.getConnectFrameLayout(), mSmallFrameLayout4, mInteractiveConnectView4.getConnectTextView());
mViewCombMap.put(mInteractiveConnectView1.getConnectTextView(), multiAlivcLiveView1);
mViewCombMap.put(mInteractiveConnectView2.getConnectTextView(), multiAlivcLiveView2);
mViewCombMap.put(mInteractiveConnectView3.getConnectTextView(), multiAlivcLiveView3);
mViewCombMap.put(mInteractiveConnectView4.getConnectTextView(), multiAlivcLiveView4);
}
private void initListener() {
mInteractiveRoomControlView.setOnClickEventListener(new InteractiveRoomControlView.OnClickEventListener() {
@Override
public void onClickSwitchCamera() {
mMultiAnchorController.switchCamera();
}
@Override
public void onClickSpeakerPhone(boolean enable) {
mMultiAnchorController.enableSpeakerPhone(enable);
}
@Override
public void onClickMuteAudio(boolean mute) {
mMultiAnchorController.setMute(mute);
}
@Override
public void onClickMuteVideo(boolean mute) {
mMultiAnchorController.muteLocalCamera(mute);
}
@Override
public void onClickEnableAudio(boolean enable) {
mMultiAnchorController.enableAudioCapture(enable);
}
@Override
public void onClickEnableVideo(boolean enable) {
mMultiAnchorController.enableLocalCamera(enable);
}
});
mConnectionLostTipsView.setConnectionLostListener(new ConnectionLostListener() {
@Override
public void onConfirm() {
runOnUiThread(new Runnable() {
@Override
public void run() {
finish();
}
});
}
});
//开始连麦
mInteractiveConnectView1.setConnectClickListener(() -> {
clickStartConnect(mInteractiveConnectView1.getConnectTextView());
});
mInteractiveConnectView2.setConnectClickListener(() -> {
clickStartConnect(mInteractiveConnectView2.getConnectTextView());
});
mInteractiveConnectView3.setConnectClickListener(() -> {
clickStartConnect(mInteractiveConnectView3.getConnectTextView());
});
mInteractiveConnectView4.setConnectClickListener(() -> {
clickStartConnect(mInteractiveConnectView4.getConnectTextView());
});
mCloseImageView.setOnClickListener(view -> {
mCurrentIntent = InteractLiveIntent.INTENT_FINISH;
showInteractLiveDialog(null, getResources().getString(R.string.interact_live_leave_room_tips), false);
});
}
private void clickStartConnect(TextView mCurrentTextView) {
if (mCurrentTextView != null && mCurrentTextView.getTag() != null && mMultiAnchorController.isOnConnected(mCurrentTextView.getTag().toString())) {
//主播端停止连麦
mCurrentIntent = InteractLiveIntent.INTENT_STOP_PULL;
showInteractLiveDialog(mCurrentTextView, getResources().getString(R.string.interact_live_connect_finish_tips), false);
} else {
//主播端开始连麦,输入用户 id
showInteractLiveDialog(mCurrentTextView, getResources().getString(R.string.interact_live_connect_tips), true);
}
}
private void changeSmallSurfaceViewVisible(boolean isShowSurfaceView, MultiAlivcLiveView alivcLiveView) {
alivcLiveView.getSmallFrameLayout().setVisibility(isShowSurfaceView ? View.VISIBLE : View.INVISIBLE);
alivcLiveView.getUnConnectFrameLayout().setVisibility(isShowSurfaceView ? View.INVISIBLE : View.VISIBLE);
}
public void updateConnectTextView(boolean connecting, TextView mConnectTextView) {
if (connecting) {
mShowConnectIdTextView.setVisibility(View.VISIBLE);
mConnectTextView.setText(getResources().getString(R.string.interact_stop_connect));
mConnectTextView.setBackground(getResources().getDrawable(R.drawable.shape_interact_live_un_connect_btn_bg));
} else {
mShowConnectIdTextView.setVisibility(View.GONE);
mConnectTextView.setText(getResources().getString(R.string.interact_start_connect));
mConnectTextView.setBackground(getResources().getDrawable(R.drawable.shape_pysh_btn_bg));
mConnectTextView.setTag("");
}
}
private void showInteractLiveDialog(TextView textView, String content, boolean showInputView) {
InteractiveCommonInputView commonInputView = new InteractiveCommonInputView(MultiInteractLiveActivity.this);
commonInputView.setViewType(InteractiveCommonInputView.ViewType.INTERACTIVE);
commonInputView.showInputView(content, showInputView);
mAUILiveDialog.setContentView(commonInputView);
mAUILiveDialog.show();
commonInputView.setOnInteractLiveTipsViewListener(new InteractLiveTipsViewListener() {
@Override
public void onCancel() {
if (mAUILiveDialog.isShowing()) {
mAUILiveDialog.dismiss();
}
}
@Override
public void onConfirm() {
if (mCurrentIntent == InteractLiveIntent.INTENT_STOP_PULL) {
//主播结束连麦
mAUILiveDialog.dismiss();
mMultiAnchorController.stopConnect((String) textView.getTag());
updateConnectTextView(false, textView);
MultiAlivcLiveView multiAlivcLiveView = mViewCombMap.get(textView);
if (multiAlivcLiveView != null) {
changeSmallSurfaceViewVisible(false, multiAlivcLiveView);
}
} else if (mCurrentIntent == InteractLiveIntent.INTENT_FINISH) {
finish();
}
}
@Override
public void onInputConfirm(InteractiveUserData userData) {
hideInputSoftFromWindowMethod(MultiInteractLiveActivity.this, commonInputView);
if (userData == null || TextUtils.isEmpty(userData.userId)) {
ToastUtils.show(getResources().getString(R.string.interact_live_connect_input_error_tips));
return;
}
userData.channelId = mAnchorUserData != null ? mAnchorUserData.channelId : "";
userData.url = AliLiveStreamURLUtil.generateInteractivePullUrl(userData.channelId, userData.userId);
mAUILiveDialog.dismiss();
//每个观众对应一个 AlvcLivePlayer
String viewKey = userData.getKey();
if (textView != null) {
textView.setTag(viewKey);
}
mIdViewCombMap.put(viewKey, mViewCombMap.get(textView));
//主播端,输入观众 id 后,开始连麦
mMultiAnchorController.startConnect(userData, Objects.requireNonNull(mViewCombMap.get(textView)).getSmallFrameLayout());
}
});
}
@Override
protected void onResume() {
super.onResume();
mMultiAnchorController.resume();
}
@Override
protected void onPause() {
super.onPause();
mMultiAnchorController.pause();
}
@Override
protected void onDestroy() {
super.onDestroy();
mMultiAnchorController.release();
}
public void hideInputSoftFromWindowMethod(Context context, View view) {
try {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,262 @@
package com.alivc.live.interactive_live;
import android.content.Context;
import android.text.TextUtils;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import com.alivc.live.commonbiz.LocalStreamReader;
import com.alivc.live.commonbiz.ResourcesConst;
import com.alivc.live.commonbiz.test.AliLiveStreamURLUtil;
import com.alivc.live.interactive_common.InteractiveMode;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.interactive_common.listener.InteractLivePushPullListener;
import com.alivc.live.interactive_common.utils.LivePushGlobalConfig;
import com.alivc.live.pusher.AlivcResolutionEnum;
import java.io.File;
import java.util.HashMap;
/**
* 以观众身份进入连麦互动界面的 Controller
*/
public class ViewerController {
private final InteractLiveManager mInteractLiveManager;
private final Context mContext;
private final LocalStreamReader mLocalStreamReader;
//主播预览 View
private FrameLayout mAnchorRenderView;
//观众连麦预览 View
private FrameLayout mViewerRenderView;
// 主播信息
private InteractiveUserData mAnchorUserData;
// 连麦观众信息
private final InteractiveUserData mViewerUserData;
//主播连麦拉流地址
private String mPullRTCUrl;
// 主播CDN拉流地址
private String mPullCDNUrl;
private boolean mNeedPullOtherStream = false;
public ViewerController(Context context, InteractiveUserData viewerUserData) {
this.mContext = context;
AlivcResolutionEnum resolution = LivePushGlobalConfig.mAlivcLivePushConfig.getResolution();
int width = AlivcResolutionEnum.getResolutionWidth(resolution, LivePushGlobalConfig.mAlivcLivePushConfig.getLivePushMode());
int height = AlivcResolutionEnum.getResolutionHeight(resolution, LivePushGlobalConfig.mAlivcLivePushConfig.getLivePushMode());
mLocalStreamReader = new LocalStreamReader.Builder()
.setVideoWith(width)
.setVideoHeight(height)
.setVideoStride(width)
.setVideoSize(height * width * 3 / 2)
.setVideoRotation(0)
.setAudioSampleRate(44100)
.setAudioChannel(1)
.setAudioBufferSize(2048)
.build();
mViewerUserData = viewerUserData;
// 1v1连麦场景下如果开启了1080P相机采集同时设置回调低分辨率texture
boolean useResolution1080P = LivePushGlobalConfig.mAlivcLivePushConfig.getResolution() == AlivcResolutionEnum.RESOLUTION_1080P;
if (useResolution1080P) {
HashMap<String, String> extras = new HashMap<>();
extras.put("user_specified_observer_texture_low_resolution", "TRUE");
LivePushGlobalConfig.mAlivcLivePushConfig.setExtras(extras);
}
mInteractLiveManager = new InteractLiveManager();
mInteractLiveManager.init(context, InteractiveMode.INTERACTIVE);
// 1v1连麦场景下如果开启了1080P相机采集同时设置回调低分辨率texture
if (useResolution1080P) {
mInteractLiveManager.changeResolution(AlivcResolutionEnum.RESOLUTION_540P);
}
}
/**
* 设置主播预览 View
*
* @param frameLayout 主播预览 View
*/
public void setAnchorRenderView(FrameLayout frameLayout) {
this.mAnchorRenderView = frameLayout;
}
/**
* 设置观众预览 View
*
* @param frameLayout 观众预览 View
*/
public void setViewerRenderView(FrameLayout frameLayout) {
this.mViewerRenderView = frameLayout;
}
/**
* 设置观看主播预览 View
*
* @param surfaceView 观看主播预览的 View
*/
public void setAnchorCDNRenderView(SurfaceView surfaceView) {
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
mInteractLiveManager.setPullView(surfaceHolder);
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
mInteractLiveManager.setPullView(null);
}
});
}
/**
* 更新主播信息
*
* @param userData
*/
public void updateAnchorUserData(InteractiveUserData userData) {
mAnchorUserData = userData;
// 连麦观看主播拉流地址RTC拉流地址
mPullRTCUrl = AliLiveStreamURLUtil.generateInteractivePullUrl(mAnchorUserData.channelId, mAnchorUserData.userId);
mAnchorUserData.url = mPullRTCUrl;
}
/**
* 观看直播
*/
public void watchLive() {
// 旁路观看主播拉流地址CDN拉流地址
mPullCDNUrl = AliLiveStreamURLUtil.generateCDNPullUrl(mAnchorUserData.channelId, mAnchorUserData.userId, LivePushGlobalConfig.mAlivcLivePushConfig.isAudioOnly());
mInteractLiveManager.startPullCDNStream(mPullCDNUrl);
mNeedPullOtherStream = false;
}
/**
* 观众连麦主播
*/
public void startConnect() {
externAV();
//停止 cdn 拉流
mInteractLiveManager.stopPullCDNStream();
//观众连麦推流
mInteractLiveManager.startPreviewAndPush(mViewerUserData, mViewerRenderView, false);
mNeedPullOtherStream = true;
}
// 先推后拉
public void pullOtherStream() {
if (mNeedPullOtherStream) {
//连麦拉流
mInteractLiveManager.setPullView(mAnchorUserData, mAnchorRenderView, true);
mInteractLiveManager.startPullRTCStream(mAnchorUserData);
}
}
private void externAV() {
if (LivePushGlobalConfig.mAlivcLivePushConfig.isExternMainStream()) {
File yuvFile = ResourcesConst.localCaptureYUVFilePath(mContext);
mLocalStreamReader.readYUVData(yuvFile, (buffer, pts, videoWidth, videoHeight, videoStride, videoSize, videoRotation) -> {
mInteractLiveManager.inputStreamVideoData(buffer, videoWidth, videoHeight, videoStride, videoSize, pts, videoRotation);
});
File pcmFile = ResourcesConst.localCapturePCMFilePath(mContext);
mLocalStreamReader.readPCMData(pcmFile, (buffer, length, pts, audioSampleRate, audioChannel) -> {
mInteractLiveManager.inputStreamAudioData(buffer, length, audioSampleRate, audioChannel, pts);
});
}
}
/**
* 结束连麦
*/
public void stopConnect() {
//观众停止推流
mInteractLiveManager.stopPush();
//大屏重新 CDN 流
mInteractLiveManager.stopPullRTCStream(mAnchorUserData);
mInteractLiveManager.startPullCDNStream(mPullCDNUrl);
}
public boolean isPushing() {
return mInteractLiveManager.isPushing();
}
public void resume() {
mInteractLiveManager.resumePush();
mInteractLiveManager.resumePlayRTCStream(mAnchorUserData);
}
public void pause() {
mInteractLiveManager.pausePush();
mInteractLiveManager.pausePlayRTCStream(mAnchorUserData);
}
public void switchCamera() {
mInteractLiveManager.switchCamera();
}
/**
* 是否有主播 id
*/
public boolean hasAnchorId() {
return mAnchorUserData != null && !TextUtils.isEmpty(mAnchorUserData.userId);
}
public void setInteractLivePushPullListener(InteractLivePushPullListener listener) {
mInteractLiveManager.setInteractLivePushPullListener(listener);
}
public void release() {
mInteractLiveManager.release();
mInteractLiveManager.setInteractLivePushPullListener(null);
mLocalStreamReader.stopYUV();
mLocalStreamReader.stopPcm();
}
public void pauseVideoPlaying() {
mInteractLiveManager.pausePlayRTCStream(mAnchorUserData);
}
public void resumeVideoPlaying() {
mInteractLiveManager.resumePlayRTCStream(mAnchorUserData);
}
public void setMute(boolean b) {
mInteractLiveManager.setMute(b);
}
public void enableAudioCapture(boolean enable) {
mInteractLiveManager.enableAudioCapture(enable);
}
public void enableSpeakerPhone(boolean enable) {
mInteractLiveManager.enableSpeakerPhone(enable);
}
public void muteLocalCamera(boolean muteLocalCamera) {
mInteractLiveManager.muteLocalCamera(muteLocalCamera);
}
public void enableLocalCamera(boolean enable) {
mInteractLiveManager.enableLocalCamera(enable);
}
public void sendSEI(String text, int payload) {
mInteractLiveManager.sendCustomMessage(text, payload);
}
}

View File

@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/big_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="@+id/big_fl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wheel_black">
<FrameLayout
android:id="@+id/small_fl"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_gravity="right|bottom"
android:layout_marginEnd="33dp"
android:layout_marginBottom="110dp" />
</FrameLayout>
<com.alivc.live.commonui.messageview.AutoScrollMessagesView
android:id="@+id/sei_receive_view"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/iv_close"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginStart="20dp"
android:src="@drawable/ic_close_white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_home_id" />
<com.alivc.live.interactive_common.widget.RoomAndUserInfoView
android:id="@+id/anchor_info_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
app:layout_constraintStart_toStartOf="@id/iv_close"
app:layout_constraintTop_toBottomOf="@id/iv_close" />
<com.alivc.live.interactive_common.widget.InteractivePaneControlView
android:id="@+id/anchor_ctrl_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="@+id/anchor_info_view"
app:layout_constraintTop_toBottomOf="@+id/anchor_info_view" />
<TextView
android:id="@+id/tv_home_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="44dp"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_show_connect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/interact_live_connecting"
android:textColor="#FCFCFD"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/tv_home_id"
app:layout_constraintStart_toStartOf="@id/tv_home_id"
app:layout_constraintTop_toBottomOf="@id/tv_home_id" />
<com.alivc.live.interactive_common.widget.InteractiveRoomControlView
android:id="@+id/interactive_setting_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_home_id"/>
<com.alivc.live.interactive_common.widget.InteractiveConnectView
android:id="@+id/connect_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="30dp"
android:layout_marginBottom="65dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/iv_beauty"
android:layout_width="22dp"
android:layout_height="18dp"
android:layout_marginTop="20dp"
android:scaleType="centerCrop"
android:src="@drawable/beauty_selector"
app:layout_constraintEnd_toEndOf="@id/interactive_setting_view"
app:layout_constraintStart_toStartOf="@id/interactive_setting_view"
app:layout_constraintTop_toBottomOf="@id/interactive_setting_view"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iv_beauty">
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/btn_show_custom_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<com.alivc.live.commonui.seiview.LivePusherSEIView
android:id="@+id/sei_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/sei_receive_view"
app:layout_constraintStart_toStartOf="parent" />
<com.alivc.live.interactive_common.widget.RoomAndUserInfoView
android:id="@+id/audience_info_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="@id/connect_view"
app:layout_constraintTop_toTopOf="@id/connect_view" />
<com.alivc.live.interactive_common.widget.InteractivePaneControlView
android:id="@+id/audience_ctrl_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="@+id/audience_info_view"
app:layout_constraintTop_toBottomOf="@+id/audience_info_view" />
<com.aliyunsdk.queen.menu.QueenBeautyMenu
android:id="@+id/beauty_beauty_menuPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/big_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="@+id/big_fl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wheel_black">
<FrameLayout
android:id="@+id/small_fl_1"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_gravity="right|bottom"
android:layout_marginEnd="140dp"
android:layout_marginBottom="310dp" />
<FrameLayout
android:id="@+id/small_fl_2"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_gravity="right|bottom"
android:layout_marginEnd="20dp"
android:layout_marginBottom="310dp" />
<FrameLayout
android:id="@+id/small_fl_3"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_gravity="right|bottom"
android:layout_marginEnd="140dp"
android:layout_marginBottom="75dp" />
<FrameLayout
android:id="@+id/small_fl_4"
android:layout_width="90dp"
android:layout_height="160dp"
android:layout_gravity="right|bottom"
android:layout_marginEnd="20dp"
android:layout_marginBottom="75dp" />
</FrameLayout>
<com.alivc.live.commonui.messageview.AutoScrollMessagesView
android:id="@+id/sei_receive_view"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iv_close" />
<ImageView
android:id="@+id/iv_close"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginStart="20dp"
android:src="@drawable/ic_close_white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_home_id" />
<TextView
android:id="@+id/tv_home_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="44dp"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_show_connect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/interact_live_connecting"
android:textColor="#FCFCFD"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/tv_home_id"
app:layout_constraintStart_toStartOf="@id/tv_home_id"
app:layout_constraintTop_toBottomOf="@id/tv_home_id" />
<com.alivc.live.interactive_common.widget.InteractiveRoomControlView
android:id="@+id/interactive_setting_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_home_id" />
<com.alivc.live.interactive_common.widget.InteractiveConnectView
android:id="@+id/connect_view_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@id/connect_view_3"
app:layout_constraintBottom_toBottomOf="@id/connect_view_2"/>
<com.alivc.live.interactive_common.widget.InteractiveConnectView
android:id="@+id/connect_view_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="30dp"
app:layout_constraintBottom_toTopOf="@id/connect_view_4"
app:layout_constraintEnd_toEndOf="@id/connect_view_4" />
<com.alivc.live.interactive_common.widget.InteractiveConnectView
android:id="@+id/connect_view_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
app:layout_constraintBottom_toBottomOf="@id/connect_view_4"
app:layout_constraintEnd_toStartOf="@id/connect_view_4" />
<com.alivc.live.interactive_common.widget.InteractiveConnectView
android:id="@+id/connect_view_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginBottom="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>