集成完直播后提交代码

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,44 @@
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 :'interactiveBareStream' ]
}
}
}
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
api project(':LiveInteractive:live_interactive_common')
implementation externalARouter
annotationProcessor externalARouterCompiler
}

View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

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

View File

@@ -0,0 +1,159 @@
package com.alivc.live.barestream_interactive;
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.InteractLiveBaseManager;
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;
class BareStreamController {
private final InteractLiveBaseManager mInteractLiveManager;
private final Context mContext;
private final LocalStreamReader mLocalStreamReader;
//主播预览 View
private FrameLayout mAnchorRenderView;
//观众连麦预览 View
private FrameLayout mViewerRenderView;
// 主播连麦推流信息
private InteractiveUserData mPushUserData;
// 观众连麦拉流信息
private InteractiveUserData mPullUserData;
public BareStreamController(Context context) {
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();
mInteractLiveManager = new InteractLiveBaseManager();
mInteractLiveManager.init(context, InteractiveMode.BARE_STREAM);
}
/**
* 设置主播预览 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(InteractiveUserData userData) {
mPushUserData = userData;
externAV();
mInteractLiveManager.startPreviewAndPush(mPushUserData, mAnchorRenderView, true);
}
public void stopPreview() {
mInteractLiveManager.stopPreview();
}
public void stopPush() {
mPushUserData = null;
mInteractLiveManager.stopPush();
}
public void stopCamera() {
mInteractLiveManager.stopCamera();
}
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 startPull(InteractiveUserData userData) {
mPullUserData = userData;
mInteractLiveManager.setPullView(mPullUserData, mViewerRenderView, false);
mInteractLiveManager.startPullRTCStream(mPullUserData);
}
// 结束拉流
public void stopPull() {
mInteractLiveManager.stopPullRTCStream(mPullUserData);
mPullUserData = null;
}
/**
* 主播是否正在连麦
*
* @return true:正在连麦 false:没有连麦
*/
public boolean isPulling() {
return mInteractLiveManager.isPulling(mPullUserData);
}
public void release() {
mInteractLiveManager.release();
mLocalStreamReader.stopYUV();
mLocalStreamReader.stopPcm();
}
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);
}
}

View File

@@ -0,0 +1,399 @@
package com.alivc.live.barestream_interactive;
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.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
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.alivc.live.commonui.utils.StatusBarUtil;
import com.alivc.live.commonutils.ToastUtils;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
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.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.player.annotations.AlivcLivePlayError;
public class InteractiveBareActivity extends AppCompatActivity {
private static final int REQ_CODE_PERMISSION = 0x1111;
private static final int REQ_CODE_PUSH_URL = 0x2222;
private static final int REQ_CODE_PULL_URL = 0x3333;
public static final String DATA_INTERACTIVE_PUSH_URL = "data_interactive_push_url";
public static final String DATA_INTERACTIVE_PULL_URL = "data_interactive_pull_url";
private AUILiveDialog mAUILiveDialog;
//Dialog 弹窗的意图
private InteractLiveIntent mCurrentIntent;
private ImageView mCloseImageView;
private TextView mShowConnectIdTextView;
//大窗口
private FrameLayout mBigFrameLayout;
//小窗口
private FrameLayout mSmallFrameLayout;
private ConnectionLostTipsView mConnectionLostTipsView;
//输入 URL 方式连麦
private InteractiveUserData mPushUserData;
private InteractiveUserData mPullUserData;
private InteractiveCommonInputView commonInputView;
private InteractiveConnectView mInteractiveConnectView;
private InteractiveRoomControlView mInteractiveRoomControlView;
private BareStreamController mBareStreamController;
private EditText mInteractivePushUrlEditText;
private ImageView mPushUrlQrImageView;
private TextView mStartPushTextView;
private boolean mIsPushing;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StatusBarUtil.translucent(this, Color.TRANSPARENT);
setContentView(R.layout.activity_interactive_bare);
InteractiveUserData pushUserData = new InteractiveUserData();
pushUserData.url = getIntent().getStringExtra(DATA_INTERACTIVE_PUSH_URL);
mPushUserData = pushUserData;
InteractiveUserData pullUserData = new InteractiveUserData();
pullUserData.url = getIntent().getStringExtra(DATA_INTERACTIVE_PULL_URL);
mPullUserData = pullUserData;
mBareStreamController = new BareStreamController(this);
initView();
initListener();
initData();
}
private void initData() {
mInteractivePushUrlEditText.setText(mPushUserData != null ? mPushUserData.url : "");
mBareStreamController.setAnchorRenderView(mBigFrameLayout);
mBareStreamController.setViewerRenderView(mSmallFrameLayout);
}
private void initView() {
mAUILiveDialog = new AUILiveDialog(this);
mInteractiveConnectView = findViewById(R.id.connect_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);
mConnectionLostTipsView = new ConnectionLostTipsView(this);
mPushUrlQrImageView = findViewById(R.id.iv_qr_push_url);
mStartPushTextView = findViewById(R.id.tv_start_push);
mInteractivePushUrlEditText = findViewById(R.id.et_interactive_push_url);
mInteractiveConnectView.setDefaultText(getResources().getString(R.string.interactive_bare_stream_start_pull));
}
private void initListener() {
mPushUrlQrImageView.setOnClickListener(view -> {
startQr(REQ_CODE_PUSH_URL);
});
mStartPushTextView.setOnClickListener(view -> {
if (mIsPushing) {
mBareStreamController.stopPush();
changePushState(false);
} else {
mPushUserData.url = mInteractivePushUrlEditText.getText().toString();
mBareStreamController.startPush(mPushUserData);
}
});
mConnectionLostTipsView.setConnectionLostListener(() -> runOnUiThread(() -> finish()));
mBareStreamController.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);
mBareStreamController.stopPull();
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();
changePushState(true);
}
@Override
public void onPushError() {
super.onPushError();
changePushState(false);
}
@Override
public void onConnectionLost() {
super.onConnectionLost();
runOnUiThread(() -> mConnectionLostTipsView.show());
}
@Override
public void onReceiveSEIMessage(int payload, byte[] data) {
super.onReceiveSEIMessage(payload, data);
}
@Override
public void onPlayerSei(int payload, byte[] uuid, byte[] data) {
super.onPlayerSei(payload, uuid, data);
}
});
//开始连麦
mInteractiveConnectView.setConnectClickListener(() -> {
if (mBareStreamController.isPulling()) {
//主播端停止连麦
mCurrentIntent = InteractLiveIntent.INTENT_STOP_PULL;
showInteractLiveDialog(getResources().getString(R.string.interact_live_connect_finish_tips), false);
} else {
//主播端开始连麦,输入用户 id
if (mPullUserData == null || TextUtils.isEmpty(mPullUserData.url)) {
showInteractLiveDialog(getResources().getString(R.string.interact_live_url_connect_tips), true);
return;
}
mBareStreamController.startPull(mPullUserData);
}
});
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() {
mBareStreamController.switchCamera();
}
@Override
public void onClickSpeakerPhone(boolean enable) {
mBareStreamController.enableSpeakerPhone(enable);
}
@Override
public void onClickMuteAudio(boolean mute) {
mBareStreamController.setMute(mute);
}
@Override
public void onClickMuteVideo(boolean mute) {
mBareStreamController.setMute(mute);
}
@Override
public void onClickEnableAudio(boolean enable) {
mBareStreamController.enableAudioCapture(enable);
}
@Override
public void onClickEnableVideo(boolean enable) {
mBareStreamController.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(getResources().getString(R.string.interactive_bare_stream_stop_pull));
} else {
mShowConnectIdTextView.setVisibility(View.GONE);
mInteractiveConnectView.unConnected(getResources().getString(R.string.interactive_bare_stream_start_pull));
}
}
private void showInteractLiveDialog(String content, boolean showInputView) {
commonInputView = new InteractiveCommonInputView(InteractiveBareActivity.this);
commonInputView.setViewType(InteractiveCommonInputView.ViewType.BARE_STREAM);
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();
mBareStreamController.stopPull();
updateConnectTextView(false);
changeSmallSurfaceViewVisible(false);
} else if (mCurrentIntent == InteractLiveIntent.INTENT_FINISH) {
finish();
}
}
@Override
public void onInputConfirm(InteractiveUserData userData) {
hideInputSoftFromWindowMethod(InteractiveBareActivity.this, commonInputView);
if (userData == null || TextUtils.isEmpty(userData.url)) {
ToastUtils.show(getResources().getString(R.string.interact_live_connect_input_error_tips));
return;
}
mAUILiveDialog.dismiss();
// 主播端,输入观众 id 后,开始连麦
mPullUserData = userData;
mBareStreamController.startPull(userData);
}
@Override
public void onQrClick() {
startQr(REQ_CODE_PULL_URL);
}
});
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
mBareStreamController.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();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQ_CODE_PUSH_URL:
case REQ_CODE_PULL_URL:
switch (resultCode) {
case RESULT_OK:
if (requestCode == REQ_CODE_PUSH_URL) {
if (mInteractivePushUrlEditText != null) {
mInteractivePushUrlEditText.setText(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
} else {
if (commonInputView != null) {
commonInputView.setQrResult(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
}
break;
case RESULT_CANCELED:
if (data != null) {
// for some reason camera is not working correctly
ToastUtils.show(getString(R.string.live_push_switch_to_facing_back_camera));
}
break;
default:
break;
}
break;
default:
break;
}
}
private void changePushState(boolean isSuccess) {
this.mIsPushing = isSuccess;
if (isSuccess) {
mStartPushTextView.setText(getResources().getString(com.alivc.live.interactive_common.R.string.interact_stop_push));
mStartPushTextView.setBackground(getResources().getDrawable(com.alivc.live.interactive_common.R.drawable.shape_interact_live_un_connect_btn_bg));
} else {
mStartPushTextView.setText(getResources().getString(com.alivc.live.interactive_common.R.string.interact_start_push));
mStartPushTextView.setBackground(getResources().getDrawable(com.alivc.live.interactive_common.R.drawable.shape_pysh_btn_bg));
}
}
private void startQr(int requestCode) {
if (ContextCompat.checkSelfPermission(InteractiveBareActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// Do not have the permission of camera, request it.
ActivityCompat.requestPermissions(InteractiveBareActivity.this, new String[]{Manifest.permission.CAMERA}, REQ_CODE_PERMISSION);
} else {
// Have gotten the permission
startCaptureActivityForResult(requestCode);
}
}
private void startCaptureActivityForResult(int requestCode) {
Intent intent = new Intent(InteractiveBareActivity.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, requestCode);
}
}

View File

@@ -0,0 +1,196 @@
package com.alivc.live.barestream_interactive;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
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.alivc.live.commonui.utils.StatusBarUtil;
import com.alivc.live.interactive_common.InteractiveSettingActivity;
import com.alivc.live.interactive_common.utils.LivePushGlobalConfig;
import com.alivc.live.interactive_common.widget.InteractiveQrEditView;
import java.util.HashMap;
public class InteractiveInputURLActivity extends AppCompatActivity {
private static final int REQ_CODE_PERMISSION = 0x1111;
private static final int REQ_CODE_PUSH_URL = 0x2222;
private static final int REQ_CODE_PULL_URL = 0x3333;
private static final int REQ_CODE_PULL_URL_1 = 0x4444;
private static final int REQ_CODE_PULL_URL_2 = 0x5555;
private static final int REQ_CODE_PULL_URL_3 = 0x6666;
private TextView mConfirmTextView;
private TextView mSettingTextView;
private ImageView mBackImageView;
private InteractiveQrEditView mInteractivePushView;
private InteractiveQrEditView mInteractivePullView;
private InteractiveQrEditView mInteractivePullView1;
private InteractiveQrEditView mInteractivePullView2;
private InteractiveQrEditView mInteractivePullView3;
private LinearLayout mMorePullViewRoot;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StatusBarUtil.translucent(this, Color.TRANSPARENT);
setContentView(R.layout.activity_interactive_input_url);
initView();
initListener();
}
private void initView() {
mBackImageView = findViewById(R.id.iv_back);
mConfirmTextView = findViewById(R.id.tv_confirm);
mSettingTextView = findViewById(R.id.tv_setting);
mMorePullViewRoot = findViewById(R.id.more_pull_view);
mInteractivePushView = findViewById(R.id.push_view);
mInteractivePullView = findViewById(R.id.pull_view);
mInteractivePullView1 = findViewById(R.id.pull_view1);
mInteractivePullView2 = findViewById(R.id.pull_view2);
mInteractivePullView3 = findViewById(R.id.pull_view3);
}
private void initListener() {
mInteractivePushView.setOnQrClickListener(() -> {
startQr(REQ_CODE_PUSH_URL);
});
mInteractivePullView.setOnQrClickListener(() -> {
startQr(REQ_CODE_PULL_URL);
});
mInteractivePullView1.setOnQrClickListener(() -> {
startQr(REQ_CODE_PULL_URL_1);
});
mInteractivePullView2.setOnQrClickListener(() -> {
startQr(REQ_CODE_PULL_URL_2);
});
mInteractivePullView3.setOnQrClickListener(() -> {
startQr(REQ_CODE_PULL_URL_3);
});
mBackImageView.setOnClickListener(view -> finish());
mConfirmTextView.setOnClickListener(view -> {
HashMap<String, String> extras = new HashMap<>();
String pushUrl = mInteractivePushView.getEditText();
String pullUrl = mInteractivePullView.getEditText();
extras.put(InteractiveBareActivity.DATA_INTERACTIVE_PUSH_URL, pushUrl);
extras.put(InteractiveBareActivity.DATA_INTERACTIVE_PULL_URL, pullUrl);
Intent intent;
if (LivePushGlobalConfig.IS_MULTI_INTERACT) {
intent = new Intent(InteractiveInputURLActivity.this, InteractiveMultiBareActivity.class);
String pullUrl1 = mInteractivePullView1.getEditText();
String pullUrl2 = mInteractivePullView2.getEditText();
String pullUrl3 = mInteractivePullView3.getEditText();
extras.put(InteractiveMultiBareActivity.DATA_INTERACTIVE_PULL_URL_1, pullUrl1);
extras.put(InteractiveMultiBareActivity.DATA_INTERACTIVE_PULL_URL_2, pullUrl2);
extras.put(InteractiveMultiBareActivity.DATA_INTERACTIVE_PULL_URL_3, pullUrl3);
} else {
intent = new Intent(InteractiveInputURLActivity.this, InteractiveBareActivity.class);
}
for (HashMap.Entry<String, String> entry : extras.entrySet()) {
intent.putExtra(entry.getKey(), entry.getValue());
}
startActivity(intent);
});
mSettingTextView.setOnClickListener(view -> {
Intent intent = new Intent(this, InteractiveSettingActivity.class);
startActivity(intent);
});
}
@Override
protected void onResume() {
super.onResume();
mMorePullViewRoot.setVisibility(LivePushGlobalConfig.IS_MULTI_INTERACT ? View.VISIBLE : View.GONE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQ_CODE_PUSH_URL:
case REQ_CODE_PULL_URL:
case REQ_CODE_PULL_URL_1:
case REQ_CODE_PULL_URL_2:
case REQ_CODE_PULL_URL_3:
switch (resultCode) {
case RESULT_OK:
if (requestCode == REQ_CODE_PUSH_URL) {
if (mInteractivePushView != null) {
mInteractivePushView.setEditText(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
} else if (requestCode == REQ_CODE_PULL_URL) {
if (mInteractivePullView != null) {
mInteractivePullView.setEditText(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
} else if (requestCode == REQ_CODE_PULL_URL_1) {
if (mInteractivePullView1 != null) {
mInteractivePullView1.setEditText(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
} else if (requestCode == REQ_CODE_PULL_URL_2) {
if (mInteractivePullView2 != null) {
mInteractivePullView2.setEditText(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
} else {
if (mInteractivePullView3 != null) {
mInteractivePullView3.setEditText(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
}
break;
default:
break;
}
break;
default:
break;
}
}
private void startQr(int requestCode) {
if (ContextCompat.checkSelfPermission(InteractiveInputURLActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// Do not have the permission of camera, request it.
ActivityCompat.requestPermissions(InteractiveInputURLActivity.this, new String[]{Manifest.permission.CAMERA}, REQ_CODE_PERMISSION);
} else {
// Have gotten the permission
startCaptureActivityForResult(requestCode);
}
}
private void startCaptureActivityForResult(int requestCode) {
Intent intent = new Intent(InteractiveInputURLActivity.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, requestCode);
}
}

View File

@@ -0,0 +1,460 @@
package com.alivc.live.barestream_interactive;
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.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
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 androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.acker.simplezxing.activity.CaptureActivity;
import com.alivc.live.barestream_interactive.adapter.InteractiveMultiBareRecyclerViewAdapter;
import com.alivc.live.barestream_interactive.bean.BareStreamBean;
import com.alivc.live.commonui.utils.StatusBarUtil;
import com.alivc.live.commonutils.ToastUtils;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.interactive_common.listener.InteractLiveTipsViewListener;
import com.alivc.live.interactive_common.listener.InteractLivePushPullListener;
import com.alivc.live.interactive_common.utils.InteractLiveIntent;
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.InteractiveRoomControlView;
import com.alivc.live.player.annotations.AlivcLivePlayError;
import java.util.ArrayList;
import java.util.List;
public class InteractiveMultiBareActivity extends AppCompatActivity {
private static final int REQ_CODE_PERMISSION = 0x1111;
private static final int REQ_CODE_PUSH_URL = 0x2222;
private static final int REQ_CODE_PULL_URL = 0x3333;
public static final String DATA_INTERACTIVE_PUSH_URL = "data_interactive_push_url";
public static final String DATA_INTERACTIVE_PULL_URL = "data_interactive_pull_url";
public static final String DATA_INTERACTIVE_PULL_URL_1 = "data_interactive_pull_url_1";
public static final String DATA_INTERACTIVE_PULL_URL_2 = "data_interactive_pull_url_2";
public static final String DATA_INTERACTIVE_PULL_URL_3 = "data_interactive_pull_url_3";
private AUILiveDialog mAUILiveDialog;
//Dialog 弹窗的意图
private InteractLiveIntent mCurrentIntent;
private ImageView mCloseImageView;
private TextView mShowConnectIdTextView;
//大窗口
private FrameLayout mBigFrameLayout;
//小窗口
private ConnectionLostTipsView mConnectionLostTipsView;
//输入 URL 方式连麦
private InteractiveUserData mPushUserData;
private InteractiveUserData mPullUserData;
private InteractiveUserData mPullUserData1;
private InteractiveUserData mPullUserData2;
private InteractiveUserData mPullUserData3;
private InteractiveCommonInputView commonInputView;
private InteractiveRoomControlView mInteractiveRoomControlView;
private MultiBareStreamController mBareStreamController;
private EditText mInteractivePushUrlEditText;
private ImageView mPushUrlQrImageView;
private TextView mStartPushTextView;
private InteractiveMultiBareRecyclerViewAdapter mAdapter;
private final List<BareStreamBean> mFakeList = new ArrayList<>();
private RecyclerView mRecyclerView;
private BareStreamBean mCurrentBareStreamBean;
private int mCurrentCheckedPosition;
private boolean mIsPushing;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StatusBarUtil.translucent(this, Color.TRANSPARENT);
setContentView(R.layout.activity_interactive_multi_bare);
InteractiveUserData pushUserData = new InteractiveUserData();
pushUserData.url = getIntent().getStringExtra(DATA_INTERACTIVE_PUSH_URL);
mPushUserData = pushUserData;
InteractiveUserData pullUserData = new InteractiveUserData();
pullUserData.url = getIntent().getStringExtra(DATA_INTERACTIVE_PULL_URL);
mPullUserData = pullUserData;
InteractiveUserData pullUserData1 = new InteractiveUserData();
pullUserData1.url = getIntent().getStringExtra(DATA_INTERACTIVE_PULL_URL_1);
mPullUserData1 = pullUserData1;
InteractiveUserData pullUserData2 = new InteractiveUserData();
pullUserData2.url = getIntent().getStringExtra(DATA_INTERACTIVE_PULL_URL_2);
mPullUserData2 = pullUserData2;
InteractiveUserData pullUserData3 = new InteractiveUserData();
pullUserData3.url = getIntent().getStringExtra(DATA_INTERACTIVE_PULL_URL_3);
mPullUserData3 = pullUserData3;
mBareStreamController = new MultiBareStreamController(this);
initView();
initListener();
initData();
}
private void initData() {
mInteractivePushUrlEditText.setText(mPushUserData != null ? mPushUserData.url : "");
mBareStreamController.setAnchorRenderView(mBigFrameLayout);
for (int i = 0; i < 4; i++) {
BareStreamBean bareStreamBean = new BareStreamBean();
switch (i) {
case 0:
bareStreamBean.setUserData(mPullUserData);
break;
case 1:
bareStreamBean.setUserData(mPullUserData1);
break;
case 2:
bareStreamBean.setUserData(mPullUserData2);
break;
case 3:
bareStreamBean.setUserData(mPullUserData3);
break;
}
mFakeList.add(bareStreamBean);
}
mAdapter.setData(mFakeList);
}
private void initView() {
mAUILiveDialog = new AUILiveDialog(this);
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);
mConnectionLostTipsView = new ConnectionLostTipsView(this);
mPushUrlQrImageView = findViewById(R.id.iv_qr_push_url);
mStartPushTextView = findViewById(R.id.tv_start_push);
mInteractivePushUrlEditText = findViewById(R.id.et_interactive_push_url);
mRecyclerView = findViewById(R.id.recycler_view);
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);
gridLayoutManager.setOrientation(GridLayoutManager.HORIZONTAL);
mRecyclerView.setLayoutManager(gridLayoutManager);
mAdapter = new InteractiveMultiBareRecyclerViewAdapter();
mRecyclerView.setAdapter(mAdapter);
}
private void initListener() {
mPushUrlQrImageView.setOnClickListener(view -> {
startQr(REQ_CODE_PUSH_URL);
});
mAdapter.setOnConnectClickListener(position -> {
mCurrentCheckedPosition = position;
mCurrentBareStreamBean = mFakeList.get(position);
if (mCurrentBareStreamBean.isConnected()) {
mCurrentIntent = InteractLiveIntent.INTENT_STOP_PULL;
showInteractLiveDialog(getResources().getString(R.string.interact_live_connect_finish_tips), false);
} else {
if (TextUtils.isEmpty(mCurrentBareStreamBean.getUrl())) {
showInteractLiveDialog(getResources().getString(R.string.interact_live_url_connect_tips), true);
} else {
FrameLayout renderViewByPosition = getRenderViewByPosition(position);
if (renderViewByPosition != null) {
mBareStreamController.startConnect(mCurrentBareStreamBean.getUserData(), renderViewByPosition);
}
}
}
});
mStartPushTextView.setOnClickListener(view -> {
if (mIsPushing) {
mBareStreamController.stopPush();
changePushState(false);
} else {
InteractiveUserData userData = new InteractiveUserData();
userData.url = mInteractivePushUrlEditText.getText().toString().trim();
mBareStreamController.setPushData(userData);
mBareStreamController.startPush();
}
});
mConnectionLostTipsView.setConnectionLostListener(() -> runOnUiThread(() -> finish()));
mBareStreamController.setMultiInteractLivePushPullListener(new InteractLivePushPullListener() {
@Override
public void onPushSuccess() {
super.onPushSuccess();
changePushState(true);
}
@Override
public void onPushError() {
super.onPushError();
changePushState(false);
}
@Override
public void onPullSuccess(InteractiveUserData userData) {
BareStreamBean bean = mFakeList.get(Integer.parseInt(userData.getKey()));
if (bean != null) {
bean.setConnected(true);
}
mAdapter.notifyDataSetChanged();
}
@Override
public void onPullError(InteractiveUserData userData, AlivcLivePlayError errorType, String errorMsg) {
super.onPullError(userData, errorType, errorMsg);
runOnUiThread(() -> {
BareStreamBean bean = mFakeList.get(Integer.parseInt(userData.getKey()));
if (bean != null) {
bean.setConnected(false);
}
mAdapter.notifyDataSetChanged();
mBareStreamController.stopConnect(userData);
updateConnectTextView(false);
ToastUtils.show(getResources().getString(R.string.interact_live_viewer_left));
});
}
@Override
public void onPullStop(InteractiveUserData userData) {
runOnUiThread(() -> {
updateConnectTextView(false);
});
}
@Override
public void onConnectionLost() {
runOnUiThread(() -> mConnectionLostTipsView.show());
}
});
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() {
mBareStreamController.switchCamera();
}
@Override
public void onClickSpeakerPhone(boolean enable) {
mBareStreamController.enableSpeakerPhone(enable);
}
@Override
public void onClickMuteAudio(boolean mute) {
mBareStreamController.setMute(mute);
}
@Override
public void onClickMuteVideo(boolean mute) {
mBareStreamController.muteLocalCamera(mute);
}
@Override
public void onClickEnableAudio(boolean enable) {
mBareStreamController.enableAudioCapture(enable);
}
@Override
public void onClickEnableVideo(boolean enable) {
mBareStreamController.enableLocalCamera(enable);
}
});
}
public void updateConnectTextView(boolean connecting) {
mShowConnectIdTextView.setVisibility(connecting ? View.VISIBLE : View.GONE);
}
private void showInteractLiveDialog(String content, boolean showInputView) {
commonInputView = new InteractiveCommonInputView(InteractiveMultiBareActivity.this);
commonInputView.setViewType(InteractiveCommonInputView.ViewType.BARE_STREAM);
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();
mBareStreamController.stopConnect(mCurrentBareStreamBean.getUserData());
updateConnectTextView(false);
mCurrentBareStreamBean.setConnected(false);
mAdapter.notifyDataSetChanged();
} else if (mCurrentIntent == InteractLiveIntent.INTENT_FINISH) {
finish();
}
}
@Override
public void onInputConfirm(InteractiveUserData userData) {
hideInputSoftFromWindowMethod(InteractiveMultiBareActivity.this, commonInputView);
if (userData == null || TextUtils.isEmpty(userData.url)) {
ToastUtils.show(getResources().getString(R.string.interact_live_connect_input_error_tips));
return;
}
mAUILiveDialog.dismiss();
//主播端,输入观众 id 后,开始连麦
mCurrentBareStreamBean.setUserData(userData);
FrameLayout renderViewByPosition = getRenderViewByPosition(mCurrentCheckedPosition);
mBareStreamController.startConnect(mCurrentBareStreamBean.getUserData(), renderViewByPosition);
}
@Override
public void onQrClick() {
startQr(REQ_CODE_PULL_URL);
}
});
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
mBareStreamController.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();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQ_CODE_PUSH_URL:
case REQ_CODE_PULL_URL:
switch (resultCode) {
case RESULT_OK:
if (requestCode == REQ_CODE_PUSH_URL) {
if (mInteractivePushUrlEditText != null) {
mInteractivePushUrlEditText.setText(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
} else {
if (commonInputView != null) {
commonInputView.setQrResult(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
}
break;
case RESULT_CANCELED:
if (data != null) {
// for some reason camera is not working correctly
ToastUtils.show(getString(R.string.live_push_switch_to_facing_back_camera));
}
break;
default:
break;
}
break;
default:
break;
}
}
private void changePushState(boolean isSuccess) {
this.mIsPushing = isSuccess;
if (isSuccess) {
mStartPushTextView.setText(getResources().getString(com.alivc.live.interactive_common.R.string.interact_stop_push));
mStartPushTextView.setBackground(getResources().getDrawable(com.alivc.live.interactive_common.R.drawable.shape_interact_live_un_connect_btn_bg));
} else {
mStartPushTextView.setText(getResources().getString(com.alivc.live.interactive_common.R.string.interact_start_push));
mStartPushTextView.setBackground(getResources().getDrawable(com.alivc.live.interactive_common.R.drawable.shape_pysh_btn_bg));
}
}
private InteractiveMultiBareRecyclerViewAdapter.InteractiveMultiBareViewHolder getViewHolderByPosition(int position) {
return (InteractiveMultiBareRecyclerViewAdapter.InteractiveMultiBareViewHolder) mRecyclerView.findViewHolderForAdapterPosition(position);
}
private FrameLayout getRenderViewByPosition(int position) {
InteractiveMultiBareRecyclerViewAdapter.InteractiveMultiBareViewHolder viewHolder = getViewHolderByPosition(position);
if (viewHolder != null) {
return viewHolder.getRenderFrameLayout();
} else {
return null;
}
}
private int getAdapterPositionFromByKey(String key) {
for (int i = 0; i < mFakeList.size(); i++) {
if (key.equals(mFakeList.get(i).getUrl())) {
return i;
}
}
return -1;
}
private void startQr(int requestCode) {
if (ContextCompat.checkSelfPermission(InteractiveMultiBareActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// Do not have the permission of camera, request it.
ActivityCompat.requestPermissions(InteractiveMultiBareActivity.this, new String[]{Manifest.permission.CAMERA}, REQ_CODE_PERMISSION);
} else {
// Have gotten the permission
startCaptureActivityForResult(requestCode);
}
}
private void startCaptureActivityForResult(int requestCode) {
Intent intent = new Intent(InteractiveMultiBareActivity.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, requestCode);
}
}

View File

@@ -0,0 +1,132 @@
package com.alivc.live.barestream_interactive;
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.InteractLiveBaseManager;
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;
class MultiBareStreamController {
private final InteractLiveBaseManager mInteractLiveManager;
private final Context mContext;
private final LocalStreamReader mLocalStreamReader;
//主播预览 View
private FrameLayout mAnchorRenderView;
//主播推流地址
private InteractiveUserData mPushUserData;
public MultiBareStreamController(Context context) {
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();
mInteractLiveManager = new InteractLiveBaseManager();
mInteractLiveManager.init(context, InteractiveMode.MULTI_BARE_STREAM);
}
public void setPushData(InteractiveUserData userData) {
mPushUserData = userData;
}
/**
* 设置主播预览 View
*
* @param frameLayout 主播预览 View
*/
public void setAnchorRenderView(FrameLayout frameLayout) {
this.mAnchorRenderView = frameLayout;
}
public void stopPush() {
mInteractLiveManager.stopPush();
}
/**
* 开始直播
*/
public void startPush() {
externAV();
mInteractLiveManager.startPreviewAndPush(mPushUserData, 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);
});
}
}
/**
* 开始连麦
*/
public void startConnect(InteractiveUserData userData, FrameLayout mRenderView) {
mInteractLiveManager.setPullView(userData, mRenderView, false);
mInteractLiveManager.startPullRTCStream(userData);
}
/**
* 结束连麦
*/
public void stopConnect(InteractiveUserData userData) {
mInteractLiveManager.stopPullRTCStream(userData);
}
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,130 @@
package com.alivc.live.barestream_interactive.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.alivc.live.barestream_interactive.R;
import com.alivc.live.barestream_interactive.bean.BareStreamBean;
import com.alivc.live.interactive_common.widget.InteractiveConnectView;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class InteractiveMultiBareRecyclerViewAdapter extends RecyclerView.Adapter<InteractiveMultiBareRecyclerViewAdapter.InteractiveMultiBareViewHolder> {
private Map<Integer, Integer> mFLayoutWithPositionMap = new HashMap<>();
private OnItemClickListener mListener;
private List<BareStreamBean> mData;
private OnItemViewChangedListener mOnPKItemViewChangedListener;
public boolean reSetFrameLayout(int position, int frameLayoutHashCode) {
if (mFLayoutWithPositionMap.containsKey(position)) {
if (frameLayoutHashCode == mFLayoutWithPositionMap.get(position)) {
return false;
} else {
mFLayoutWithPositionMap.put(position, frameLayoutHashCode);
return true;
}
}
return false;
}
@Override
public void onViewAttachedToWindow(InteractiveMultiBareViewHolder holder) {
super.onViewAttachedToWindow(holder);
if (mOnPKItemViewChangedListener != null) {
mOnPKItemViewChangedListener.onItemViewAttachedToWindow(holder.getAdapterPosition());
}
}
@Override
public void onViewDetachedFromWindow(InteractiveMultiBareViewHolder holder) {
super.onViewDetachedFromWindow(holder);
if (mOnPKItemViewChangedListener != null) {
mOnPKItemViewChangedListener.onItemViewDetachedToWindow(holder.getAdapterPosition());
}
}
@NonNull
@Override
public InteractiveMultiBareViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_multi_interactive_bare_item, parent, false);
return new InteractiveMultiBareViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull InteractiveMultiBareViewHolder holder, int position) {
BareStreamBean bareStreamBean = mData.get(position);
holder.isConnected(bareStreamBean.isConnected());
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public void setOnConnectClickListener(OnItemClickListener listener) {
this.mListener = listener;
}
public void setOnItemViewChangedListener(OnItemViewChangedListener listener) {
this.mOnPKItemViewChangedListener = listener;
}
public void setData(List<BareStreamBean> dataList) {
this.mData = dataList;
notifyDataSetChanged();
}
public class InteractiveMultiBareViewHolder extends RecyclerView.ViewHolder {
private final InteractiveConnectView mConnectView;
private final FrameLayout mRenderFrameLayout;
public InteractiveMultiBareViewHolder(@NonNull View itemView) {
super(itemView);
mConnectView = itemView.findViewById(R.id.connect_view);
mRenderFrameLayout = itemView.findViewById(R.id.render_frame);
mConnectView.setDefaultText(mConnectView.getResources().getString(R.string.interactive_bare_stream_start_pull));
mConnectView.setConnectClickListener(() -> {
if (mListener != null) {
mListener.onConnectClick(getAdapterPosition());
}
});
}
public FrameLayout getRenderFrameLayout() {
return mRenderFrameLayout;
}
public InteractiveConnectView getConnectView() {
return mConnectView;
}
public void isConnected(boolean isConnected) {
if (isConnected) {
mConnectView.connected(mConnectView.getResources().getString(R.string.interactive_bare_stream_stop_pull));
} else {
mConnectView.unConnected(mConnectView.getResources().getString(R.string.interactive_bare_stream_start_pull));
}
mConnectView.getConnectFrameLayout().setVisibility(isConnected ? View.INVISIBLE : View.VISIBLE);
}
}
public interface OnItemClickListener {
void onConnectClick(int position);
}
public interface OnItemViewChangedListener {
void onItemViewAttachedToWindow(int position);
void onItemViewDetachedToWindow(int position);
}
}

View File

@@ -0,0 +1,29 @@
package com.alivc.live.barestream_interactive.bean;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
public class BareStreamBean {
private InteractiveUserData userData;
private boolean isConnected;
public InteractiveUserData getUserData() {
return userData;
}
public void setUserData(InteractiveUserData userData) {
this.userData = userData;
}
public String getUrl() {
return userData != null ? userData.url : null;
}
public boolean isConnected() {
return isConnected;
}
public void setConnected(boolean connected) {
isConnected = connected;
}
}

View File

@@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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>
<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" />
<TextView
android:id="@+id/tv_interactive_push_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="22dp"
android:layout_marginStart="20dp"
android:text="@string/interact_live_push_url"
android:gravity="center"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_show_connect"/>
<EditText
android:id="@+id/et_interactive_push_url"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxLines="1"
android:singleLine="true"
android:textColor="#E6E7EC"
android:textColorHint="#E6E7EC"
android:theme="@style/AUIEditText"
app:layout_constraintEnd_toStartOf="@id/tv_start_push"
app:layout_constraintStart_toStartOf="@id/tv_interactive_push_url"
app:layout_constraintTop_toBottomOf="@id/tv_interactive_push_url" />
<ImageView
android:id="@+id/iv_qr_push_url"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@color/transparent"
android:layout_margin="5dp"
android:src="@drawable/ic_scan_icon"
app:layout_constraintBottom_toBottomOf="@id/et_interactive_push_url"
app:layout_constraintEnd_toEndOf="@id/et_interactive_push_url"
app:layout_constraintTop_toTopOf="@id/et_interactive_push_url" />
<TextView
android:id="@+id/tv_start_push"
android:layout_width="94dp"
android:layout_height="30dp"
android:background="@drawable/shape_pysh_btn_bg"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="1"
android:text="@string/interact_start_push"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_constraintTop_toTopOf="@id/et_interactive_push_url"
app:layout_constraintBottom_toBottomOf="@id/et_interactive_push_url"
app:layout_constraintEnd_toEndOf="parent"/>
<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"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_interactive_push_url"/>
<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" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1D22">
<ImageView
android:id="@+id/iv_back"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginStart="20dp"
android:src="@drawable/ic_live_action_bar_back"
app:layout_constraintBottom_toBottomOf="@id/tv_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_title" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginTop="44dp"
android:gravity="center"
android:text="@string/interact_live_url_title"
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_setting"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginTop="44dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:text="@string/interact_setting"
android:textColor="#FCFCFD"
android:textSize="12sp"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="44dp"
android:background="@drawable/shape_rect_blue"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="2"
android:text="@string/interact_live_confirm"
android:textColor="@android:color/white"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.alivc.live.interactive_common.widget.InteractiveQrEditView
android:id="@+id/push_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="@string/interact_live_push_url" />
<com.alivc.live.interactive_common.widget.InteractiveQrEditView
android:id="@+id/pull_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="@id/push_view"
app:layout_constraintStart_toStartOf="@id/push_view"
app:layout_constraintTop_toBottomOf="@id/push_view"
app:title="@string/interact_live_pull_url" />
<LinearLayout
android:id="@+id/more_pull_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/pull_view">
<com.alivc.live.interactive_common.widget.InteractiveQrEditView
android:id="@+id/pull_view1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="@id/push_view"
app:layout_constraintStart_toStartOf="@id/push_view"
app:layout_constraintTop_toBottomOf="@id/push_view"
app:title="@string/interact_live_pull_url" />
<com.alivc.live.interactive_common.widget.InteractiveQrEditView
android:id="@+id/pull_view2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="@id/push_view"
app:layout_constraintStart_toStartOf="@id/push_view"
app:layout_constraintTop_toBottomOf="@id/push_view"
app:title="@string/interact_live_pull_url" />
<com.alivc.live.interactive_common.widget.InteractiveQrEditView
android:id="@+id/pull_view3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="@id/push_view"
app:layout_constraintStart_toStartOf="@id/push_view"
app:layout_constraintTop_toBottomOf="@id/push_view"
app:title="@string/interact_live_pull_url" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/big_fl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wheel_black">
</FrameLayout>
<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" />
<TextView
android:id="@+id/tv_interactive_push_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="22dp"
android:gravity="center"
android:text="@string/interact_live_push_url"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_show_connect" />
<EditText
android:id="@+id/et_interactive_push_url"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxLines="1"
android:singleLine="true"
android:textColor="#E6E7EC"
android:textColorHint="#E6E7EC"
android:theme="@style/AUIEditText"
app:layout_constraintEnd_toStartOf="@id/tv_start_push"
app:layout_constraintStart_toStartOf="@id/tv_interactive_push_url"
app:layout_constraintTop_toBottomOf="@id/tv_interactive_push_url" />
<ImageView
android:id="@+id/iv_qr_push_url"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="5dp"
android:background="@color/transparent"
android:src="@drawable/ic_scan_icon"
app:layout_constraintBottom_toBottomOf="@id/et_interactive_push_url"
app:layout_constraintEnd_toEndOf="@id/et_interactive_push_url"
app:layout_constraintTop_toTopOf="@id/et_interactive_push_url" />
<TextView
android:id="@+id/tv_start_push"
android:layout_width="94dp"
android:layout_height="30dp"
android:background="@drawable/shape_pysh_btn_bg"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="1"
android:text="@string/interact_start_push"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/et_interactive_push_url"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/et_interactive_push_url" />
<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_marginTop="20dp"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_interactive_push_url" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:id="@+id/render_frame"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="w,16:9"
android:background="@color/alivc_color_black"
app:layout_constraintStart_toStartOf="@id/connect_view"
app:layout_constraintEnd_toEndOf="@id/connect_view"
app:layout_constraintTop_toTopOf="parent">
</FrameLayout>
<com.alivc.live.interactive_common.widget.InteractiveConnectView
android:id="@+id/connect_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,4 @@
<resources>
<string name="interactive_bare_stream_start_pull">开始拉流</string>
<string name="interactive_bare_stream_stop_pull">结束拉流</string>
</resources>

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>

View File

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

View File

@@ -0,0 +1,55 @@
import java.text.SimpleDateFormat
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 :'interactiveCommon' ]
}
}
}
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
api project(':LiveCommon:live_commonbiz')
api project(':LiveBeauty:live_queenbeauty')
if (project.hasProperty("sdk_type") && "AliVCSDK_InteractiveLive".equalsIgnoreCase(sdk_type)) {
api externalLivePusherInteractive
} else {
api externalLivePusher
}
implementation externalSimpleZXing
implementation externalARouter
annotationProcessor externalARouterCompiler
}

View File

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

View File

@@ -0,0 +1,103 @@
package com.alivc.live.interactive_common;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.Fragment;
import com.alivc.live.commonbiz.SharedPreferenceUtils;
/**
* AppInfo 相关信息录入 Fragment
*/
public class InteractLiveAppInfoFragment extends Fragment {
private TextView mAppInfoTv;
private ImageView mArrowImageView;
private ConstraintLayout mAppInfoConstraintLayout;
private ImageView mEditImageView;
private TextView mAppIdTextView;
private TextView mAppKeyTextView;
private TextView mPlayDomainTextView;
private OnEditClickListener mOnEditClickListener;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_interact_live_appinfo, container, false);
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mAppInfoTv = view.findViewById(R.id.tv_app_info);
mArrowImageView = view.findViewById(R.id.iv_arrow);
mEditImageView = view.findViewById(R.id.iv_edit);
mAppInfoConstraintLayout = view.findViewById(R.id.cl_app_info);
mAppIdTextView = view.findViewById(R.id.tv_app_id);
mAppKeyTextView = view.findViewById(R.id.tv_app_key);
mPlayDomainTextView = view.findViewById(R.id.tv_play_domain);
mArrowImageView.setColorFilter(Color.WHITE);
initListener();
}
@Override
public void onResume() {
super.onResume();
mAppIdTextView.setText(SharedPreferenceUtils.getAppId(getContext().getApplicationContext()));
mAppKeyTextView.setText(SharedPreferenceUtils.getAppKey(getContext().getApplicationContext()));
mPlayDomainTextView.setText(SharedPreferenceUtils.getPlayDomain(getContext().getApplicationContext()));
}
private void initListener() {
// 测试用长按应用信息四个字自动填写用户ID和房间号
mAppInfoTv.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mOnEditClickListener != null) {
mOnEditClickListener.onLongClickAppInfoForDebug();
}
return false;
}
});
mArrowImageView.setOnClickListener((view) -> {
float mArrowImageViewRotation;
if (mAppInfoConstraintLayout.isShown()) {
mAppInfoConstraintLayout.setVisibility(View.GONE);
mArrowImageViewRotation = 0;
} else {
mAppInfoConstraintLayout.setVisibility(View.VISIBLE);
mArrowImageViewRotation = 180;
}
mArrowImageView.setRotation(mArrowImageViewRotation);
});
mEditImageView.setOnClickListener((view) -> {
if (mOnEditClickListener != null) {
mOnEditClickListener.onEditClickListener();
}
});
}
public interface OnEditClickListener {
void onLongClickAppInfoForDebug();
void onEditClickListener();
}
public void setOnEditClickListener(OnEditClickListener listener) {
this.mOnEditClickListener = listener;
}
}

View File

@@ -0,0 +1,1211 @@
package com.alivc.live.interactive_common;
import static com.alivc.live.interactive_common.utils.LivePushGlobalConfig.mAlivcLivePushConfig;
import android.content.Context;
import android.graphics.Bitmap;
import android.hardware.Camera;
import android.text.TextUtils;
import android.util.Log;
import android.widget.FrameLayout;
import com.alivc.component.custom.AlivcLivePushCustomFilter;
import com.alivc.live.annotations.AlivcLiveAudioFrameObserverOperationMode;
import com.alivc.live.annotations.AlivcLiveAudioFrameObserverUserDefinedInfoBitMask;
import com.alivc.live.annotations.AlivcLiveAudioSource;
import com.alivc.live.annotations.AlivcLiveMode;
import com.alivc.live.annotations.AlivcLiveMuteLocalAudioMode;
import com.alivc.live.annotations.AlivcLiveNetworkQuality;
import com.alivc.live.annotations.AlivcLivePushKickedOutType;
import com.alivc.live.annotations.AlivcLiveRecordMediaEvent;
import com.alivc.live.beauty.BeautyFactory;
import com.alivc.live.beauty.BeautyInterface;
import com.alivc.live.beauty.constant.BeautySDKType;
import com.alivc.live.commonbiz.backdoor.BackDoorInstance;
import com.alivc.live.commonbiz.seidelay.SEIDelayManager;
import com.alivc.live.commonbiz.seidelay.SEISourceType;
import com.alivc.live.commonbiz.seidelay.api.ISEIDelayEventListener;
import com.alivc.live.commonbiz.test.AliLiveStreamURLUtil;
import com.alivc.live.commonutils.ToastUtils;
import com.alivc.live.interactive_common.bean.InteractiveLivePlayer;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.interactive_common.listener.InteractLivePushPullListener;
import com.alivc.live.interactive_common.manager.TimestampWatermarkManager;
import com.alivc.live.interactive_common.utils.LivePushGlobalConfig;
import com.alivc.live.player.AlivcLivePlayConfig;
import com.alivc.live.player.AlivcLivePlayer;
import com.alivc.live.player.annotations.AlivcLivePlayError;
import com.alivc.live.player.annotations.AlivcLivePlayVideoStreamType;
import com.alivc.live.pusher.AlivcAudioChannelEnum;
import com.alivc.live.pusher.AlivcAudioSampleRateEnum;
import com.alivc.live.pusher.AlivcImageFormat;
import com.alivc.live.pusher.AlivcLiveAudioEffectConfig;
import com.alivc.live.pusher.AlivcLiveAudioFrameObserverConfig;
import com.alivc.live.pusher.AlivcLiveBase;
import com.alivc.live.pusher.AlivcLiveLocalRecordConfig;
import com.alivc.live.pusher.AlivcLiveMixStream;
import com.alivc.live.pusher.AlivcLivePublishState;
import com.alivc.live.pusher.AlivcLivePushAudioEffectVoiceChangeMode;
import com.alivc.live.pusher.AlivcLivePushAudioFrame;
import com.alivc.live.pusher.AlivcLivePushAudioFrameListener;
import com.alivc.live.pusher.AlivcLivePushError;
import com.alivc.live.pusher.AlivcLivePushErrorListener;
import com.alivc.live.pusher.AlivcLivePushExternalAudioStreamConfig;
import com.alivc.live.pusher.AlivcLivePushInfoListener;
import com.alivc.live.pusher.AlivcLivePushNetworkListener;
import com.alivc.live.pusher.AlivcLivePushStatsInfo;
import com.alivc.live.pusher.AlivcLivePushVideoConfig;
import com.alivc.live.pusher.AlivcLivePusher;
import com.alivc.live.pusher.AlivcLivePusherRawDataSample;
import com.alivc.live.pusher.AlivcLiveTranscodingConfig;
import com.alivc.live.pusher.AlivcPreviewDisplayMode;
import com.alivc.live.pusher.AlivcResolutionEnum;
import com.alivc.live.pusher.AlivcSoundFormat;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* 直播连麦基础类
*
* @note 请参考 API 文档,了解对应接口的释义,辅助接入
* @note 互动模式下,请使用`AlivcRTC`作为日志tag进行自主排障。
*/
public class InteractLiveBaseManager {
static {
// 1. 注册推流SDK
AlivcLiveBase.registerSDK();
}
protected static final String TAG = "InteractLiveBaseManager";
protected InteractiveMode mInteractiveMode;
// 这个TM用来做混流的
protected FrameLayout mAudienceFrameLayout;
//多人连麦混流
protected final ArrayList<AlivcLiveMixStream> mMultiInteractLiveMixStreamsArray = new ArrayList<>();
//多人连麦 Config
protected final AlivcLiveTranscodingConfig mMixInteractLiveTranscodingConfig = new AlivcLiveTranscodingConfig();
protected Context mContext;
protected AlivcLivePusher mAlivcLivePusher;
private InteractiveUserData mPushUserData;
protected final Map<String, InteractiveLivePlayer> mInteractiveLivePlayerMap = new HashMap<>();
protected final Map<String, InteractiveUserData> mInteractiveUserDataMap = new HashMap<>();
protected InteractLivePushPullListener mInteractLivePushPullListener;
// 美颜处理类需对接Queen美颜SDK且拥有License拥有美颜能力
private BeautyInterface mBeautyManager;
// CameraId美颜需要使用
private int mCameraId;
// 互动模式下的水印处理类
private TimestampWatermarkManager mTimestampWatermarkManager;
protected final SEIDelayManager mSEIDelayManager = new SEIDelayManager();
public void init(Context context, InteractiveMode interactiveMode) {
mInteractiveMode = interactiveMode;
mContext = context.getApplicationContext();
initLivePusher(interactiveMode);
// initWatermarks();
mSEIDelayManager.registerReceiver(new ISEIDelayEventListener() {
@Override
public void onEvent(String src, String type, String msg) {
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onReceiveSEIDelay(src, type, msg);
}
}
});
}
/**
* 2. 初始化 AlivcLivePushConfig 和 AlivcLivePusher
*
* @param interactiveMode 直播连麦模式直播连麦、RTS 2.0推拉裸流)
*/
private void initLivePusher(InteractiveMode interactiveMode) {
// 初始化推流配置类
// 设置推流模式为 AlivcLiveInteractiveMode互动模式互动直播支持连麦、PK等实时互动
mAlivcLivePushConfig.setLivePushMode(AlivcLiveMode.AlivcLiveInteractiveMode);
// 互动模式下如需与Web连麦互通必须使用H5兼容模式否则Web用户查看Native用户将是黑屏。
mAlivcLivePushConfig.setH5CompatibleMode(LivePushGlobalConfig.IS_H5_COMPATIBLE);
// 互动模式下开启RTS推拉裸流(直推&直拉,不同于直播连麦)
if (InteractiveMode.isBareStream(interactiveMode)) {
mAlivcLivePushConfig.setEnableRTSForInteractiveMode(true);
}
/// 如果是外部音视频推流,需要初始化的 Pusher 配置以及自定义音频、视频的Profiles
if (mAlivcLivePushConfig.isExternMainStream()) {
mAlivcLivePushConfig.setExternMainStream(true, AlivcImageFormat.IMAGE_FORMAT_YUVNV12, AlivcSoundFormat.SOUND_FORMAT_S16);
mAlivcLivePushConfig.setAudioChannels(AlivcAudioChannelEnum.AUDIO_CHANNEL_ONE);
mAlivcLivePushConfig.setAudioSampleRate(AlivcAudioSampleRateEnum.AUDIO_SAMPLE_RATE_44100);
}
// 如果需要开启 Data Channel 自定义消息通道,以代替 SEI 功能,需要预先开启 Data Channel 自定义消息通道
if (LivePushGlobalConfig.IS_DATA_CHANNEL_MESSAGE_ENABLE) {
mAlivcLivePushConfig.setEnableDataChannelMessage(true);
}
// 开启强制耳返
HashMap<String, String> earbackExtras = new HashMap<>(4);
earbackExtras.put("earback_open_when_without_headset", LivePushGlobalConfig.IS_EARBACK_OPEN_WITHOUT_HEADSET ? "TRUE" : "FALSE");
mAlivcLivePushConfig.setExtras(earbackExtras);
// Demo逻辑用于测试请勿follow如果强制RTC预发环境那么增加extra配置
if (BackDoorInstance.getInstance().isForceRTCPreEnvironment()) {
HashMap<String, String> extras = new HashMap<>(4);
extras.put("user_specified_environment", "PRE_RELEASE");
mAlivcLivePushConfig.setExtras(extras);
}
// 初始化推流引擎
mAlivcLivePusher = new AlivcLivePusher();
mAlivcLivePusher.init(mContext.getApplicationContext(), mAlivcLivePushConfig);
// 设置音量回调频率和平滑系数
mAlivcLivePusher.enableAudioVolumeIndication(300, 3, 1);
/*
* 错误异常及特殊场景处理
* 参考文档https://help.aliyun.com/zh/live/developer-reference/handling-of-exceptions-and-special-scenarios-for-android
*/
// 设置推流错误事件
mAlivcLivePusher.setLivePushErrorListener(new AlivcLivePushErrorListener() {
@Override
public void onSystemError(AlivcLivePusher alivcLivePusher, AlivcLivePushError alivcLivePushError) {
Log.e(TAG, "onSystemError: " + alivcLivePushError);
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onPushError();
}
}
@Override
public void onSDKError(AlivcLivePusher alivcLivePusher, AlivcLivePushError alivcLivePushError) {
Log.e(TAG, "onSDKError: " + alivcLivePushError);
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onPushError();
}
}
});
// 设置推流通知事件
mAlivcLivePusher.setLivePushInfoListener(new AlivcLivePushInfoListener() {
@Override
public void onPreviewStarted(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onPreviewStarted: ");
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onPushSuccess();
}
}
@Override
public void onPreviewStopped(AlivcLivePusher pusher) {
Log.d(TAG, "onPreviewStopped: ");
}
@Override
public void onPushStarted(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onPushStarted: ");
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onPushSuccess();
}
}
@Override
public void onPushPaused(AlivcLivePusher pusher) {
Log.d(TAG, "onPushPaused: ");
}
@Override
public void onPushResumed(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onPushResumed: ");
}
@Override
public void onPushStopped(AlivcLivePusher pusher) {
Log.d(TAG, "onPushStopped: ");
pusher.stopPreview();
}
@Override
public void onPushRestarted(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onPushRestarted: ");
}
@Override
public void onFirstFramePushed(AlivcLivePusher pusher) {
Log.d(TAG, "onFirstFramePushed: ");
}
@Override
public void onFirstFramePreviewed(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onFirstFramePreviewed: ");
}
@Override
public void onDropFrame(AlivcLivePusher alivcLivePusher, int i, int i1) {
Log.d(TAG, "onDropFrame: ");
}
@Override
public void onAdjustBitrate(AlivcLivePusher pusher, int currentBitrate, int targetBitrate) {
Log.i(TAG, "onAdjustBitrate: " + currentBitrate + "->" + targetBitrate);
}
@Override
public void onAdjustFps(AlivcLivePusher alivcLivePusher, int i, int i1) {
Log.d(TAG, "onAdjustFps: ");
}
@Override
public void onPushStatistics(AlivcLivePusher alivcLivePusher, AlivcLivePushStatsInfo alivcLivePushStatsInfo) {
// Log.i(TAG, "onPushStatistics: " + alivcLivePushStatsInfo);
}
@Override
public void onSetLiveMixTranscodingConfig(AlivcLivePusher alivcLivePusher, boolean b, String s) {
Log.d(TAG, "onSetLiveMixTranscodingConfig: ");
}
@Override
public void onKickedOutByServer(AlivcLivePusher pusher, AlivcLivePushKickedOutType kickedOutType) {
Log.d(TAG, "onKickedOutByServer: " + kickedOutType);
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onPushError();
}
}
@Override
public void onMicrophoneVolumeUpdate(AlivcLivePusher pusher, int volume) {
// 麦克风音量回调仅互动模式下生效需设置AlivcLivePusher#enableAudioVolumeIndication接口
// Log.d(TAG, "onMicrophoneVolumeUpdate: " + volume);
}
@Override
public void onLocalRecordEvent(AlivcLiveRecordMediaEvent mediaEvent, String storagePath) {
ToastUtils.show(mediaEvent + ", " + storagePath);
}
@Override
public void onRemoteUserEnterRoom(AlivcLivePusher pusher, String userId, boolean isOnline) {
ToastUtils.show("onRemoteUserEnterRoom: " + userId + ", isOnline" + isOnline);
}
@Override
public void onRemoteUserAudioStream(AlivcLivePusher pusher, String userId, boolean isPushing) {
ToastUtils.show("onRemoteUserAudioStream: " + userId + ", isPushing: " + isPushing);
}
@Override
public void onRemoteUserVideoStream(AlivcLivePusher pusher, String userId, AlivcLivePlayVideoStreamType videoStreamType, boolean isPushing) {
ToastUtils.show("onRemoteUserVideoStream: " + userId + ", videoStreamType: " + videoStreamType + ", isPushing: " + isPushing);
}
@Override
public void onAudioPublishStateChanged(AlivcLivePublishState oldState, AlivcLivePublishState newState) {
super.onAudioPublishStateChanged(oldState, newState);
ToastUtils.show("onAudioPublishStateChanged: " + oldState + "->" + newState);
}
@Override
public void onVideoPublishStateChanged(AlivcLivePublishState oldState, AlivcLivePublishState newState) {
super.onVideoPublishStateChanged(oldState, newState);
ToastUtils.show("onVideoPublishStateChanged: " + oldState + "->" + newState);
}
@Override
public void onScreenSharePublishStateChanged(AlivcLivePublishState oldState, AlivcLivePublishState newState) {
super.onScreenSharePublishStateChanged(oldState, newState);
ToastUtils.show("onScreenSharePublishStateChanged: " + oldState + "->" + newState);
}
@Override
public void onLocalDualAudioStreamPushState(AlivcLivePusher pusher, boolean isPushing) {
super.onLocalDualAudioStreamPushState(pusher, isPushing);
ToastUtils.show("onLocalDualAudioStreamPushState: " + isPushing);
}
});
// 设置网络通知事件
mAlivcLivePusher.setLivePushNetworkListener(new AlivcLivePushNetworkListener() {
@Override
public void onNetworkPoor(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onNetworkPoor: ");
}
@Override
public void onNetworkRecovery(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onNetworkRecovery: ");
}
@Override
public void onReconnectStart(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onReconnectStart: ");
}
@Override
public void onConnectionLost(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onConnectionLost: ");
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onConnectionLost();
}
}
@Override
public void onReconnectFail(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onReconnectFail: ");
}
@Override
public void onReconnectSucceed(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onReconnectSucceed: ");
}
@Override
public void onSendDataTimeout(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onSendDataTimeout: ");
}
@Override
public void onConnectFail(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onConnectFail: ");
}
@Override
public void onNetworkQualityChanged(AlivcLiveNetworkQuality upQuality, AlivcLiveNetworkQuality downQuality) {
}
@Override
public String onPushURLAuthenticationOverdue(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onPushURLAuthenticationOverdue: ");
return null;
}
@Override
public void onPushURLTokenWillExpire(AlivcLivePusher pusher) {
Log.d(TAG, "onPushURLTokenWillExpire: ");
}
@Override
public void onPushURLTokenExpired(AlivcLivePusher pusher) {
Log.d(TAG, "onPushURLTokenExpired: ");
}
@Override
public void onSendMessage(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onSendMessage: ");
}
@Override
public void onPacketsLost(AlivcLivePusher alivcLivePusher) {
Log.d(TAG, "onPacketsLost: ");
}
});
// 【美颜】注册视频前处理回调
mAlivcLivePusher.setCustomFilter(new AlivcLivePushCustomFilter() {
@Override
public void customFilterCreate() {
initBeautyManager();
}
@Override
public int customFilterProcess(int inputTexture, int textureWidth, int textureHeight, long extra) {
if (mBeautyManager == null) {
return inputTexture;
}
return mBeautyManager.onTextureInput(inputTexture, textureWidth, textureHeight);
}
@Override
public void customFilterDestroy() {
destroyBeautyManager();
}
});
// 记录当前摄像头状态
mCameraId = mAlivcLivePushConfig.getCameraType();
}
private void destroyLivePusher() {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.destroy();
}
}
public boolean isPushing() {
return mAlivcLivePusher != null && mAlivcLivePusher.isPushing();
}
public boolean isPulling(InteractiveUserData userData) {
if (userData == null) {
return false;
}
InteractiveLivePlayer interactiveLivePlayer = mInteractiveLivePlayerMap.get(userData.getKey());
if (interactiveLivePlayer != null) {
return interactiveLivePlayer.isPulling();
}
return false;
}
private void initWatermarks() {
// 互动模式下使用该接口添加水印,最多添加一个,多则替代
mTimestampWatermarkManager = new TimestampWatermarkManager();
mTimestampWatermarkManager.init(new TimestampWatermarkManager.OnWatermarkListener() {
@Override
public void onWatermarkUpdate(Bitmap bitmap) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.addWaterMark(bitmap, 0, 0, 1);
}
}
});
}
private void destroyWatermarks() {
if (mTimestampWatermarkManager != null) {
mTimestampWatermarkManager.destroy();
mTimestampWatermarkManager = null;
}
}
public void setInteractLivePushPullListener(InteractLivePushPullListener listener) {
mInteractLivePushPullListener = listener;
}
private boolean createAlivcLivePlayer(InteractiveUserData userData) {
if (userData == null) {
return false;
}
String userDataKey = userData.getKey();
if (mInteractiveLivePlayerMap.containsKey(userDataKey)) {
Log.i(TAG, "createAlivcLivePlayer already created " + userDataKey);
return false;
}
Log.d(TAG, "createAlivcLivePlayer: " + userDataKey);
InteractiveLivePlayer alivcLivePlayer = new InteractiveLivePlayer(mContext, AlivcLiveMode.AlivcLiveInteractiveMode);
alivcLivePlayer.setPullUserData(userData);
alivcLivePlayer.setMultiInteractPlayInfoListener(new InteractLivePushPullListener() {
@Override
public void onPullSuccess(InteractiveUserData userData) {
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onPullSuccess(userData);
}
}
@Override
public void onPullError(InteractiveUserData userData, AlivcLivePlayError errorType, String errorMsg) {
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onPullError(userData, errorType, errorMsg);
}
}
@Override
public void onPullStop(InteractiveUserData userData) {
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onPullStop(userData);
}
}
@Override
public void onPushSuccess() {
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onPushSuccess();
}
}
@Override
public void onPushError() {
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onPushError();
}
}
@Override
public void onReceiveSEIMessage(int payload, byte[] data) {
super.onReceiveSEIMessage(payload, data);
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onReceiveSEIMessage(payload, data);
}
String sei = new String(data, StandardCharsets.UTF_8);
mSEIDelayManager.receiveSEI(SEISourceType.RTC, sei);
}
@Override
public void onVideoEnabled(boolean enable) {
super.onVideoEnabled(enable);
if (mInteractLivePushPullListener != null) {
mInteractLivePushPullListener.onVideoEnabled(enable);
}
}
});
mInteractiveLivePlayerMap.put(userDataKey, alivcLivePlayer);
mInteractiveUserDataMap.put(userDataKey, userData);
return true;
}
public void startPreviewAndPush(InteractiveUserData userData, FrameLayout frameLayout, boolean isAnchor) {
if (userData == null) {
Log.e(TAG, "startPreviewAndPush, userData invalid!");
return;
}
mPushUserData = userData;
mAlivcLivePusher.startPreview(mContext, frameLayout, isAnchor);
String pushUrl = userData.url;
if (TextUtils.isEmpty(pushUrl) && InteractiveMode.isInteractive(mInteractiveMode)) {
pushUrl = AliLiveStreamURLUtil.generateInteractivePushUrl(userData.channelId, userData.userId);
}
if (TextUtils.isEmpty(pushUrl)) {
Log.e(TAG, "startPreviewAndPush, pushUrl invalid!");
return;
}
Log.d(TAG, "startPreviewAndPush: " + isAnchor + ", " + pushUrl);
mAlivcLivePusher.startPushAsync(pushUrl);
mSEIDelayManager.registerProvider(userData.userId, new ISEIDelayEventListener() {
@Override
public void onEvent(String src, String type, String msg) {
sendCustomMessage(msg, 5);
}
});
}
public void stopPreview() {
mAlivcLivePusher.stopPreview();
}
public void updatePreview(FrameLayout frameLayout, boolean isFullScreen) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.updatePreview(mContext, frameLayout, isFullScreen);
}
}
public void stopPush() {
if (isPushing()) {
mAlivcLivePusher.stopPush();
}
}
public void stopCamera() {
mAlivcLivePusher.stopCamera();
}
public void setPullView(InteractiveUserData userData, FrameLayout frameLayout, boolean isAnchor) {
if (userData == null) {
return;
}
this.mAudienceFrameLayout = frameLayout;
boolean isNew = createAlivcLivePlayer(userData);
AlivcLivePlayer livePlayer = mInteractiveLivePlayerMap.get(userData.getKey());
if (livePlayer == null) {
Log.e(TAG, "setPullView error: livePlayer is empty");
return;
}
if (isNew) {
AlivcLivePlayConfig config = new AlivcLivePlayConfig();
config.isFullScreen = isAnchor;
config.videoStreamType = userData.videoStreamType;
config.audioStreamType = userData.audioStreamType;
livePlayer.setupWithConfig(config);
}
livePlayer.setPlayView(frameLayout);
Log.i(TAG, "setPullView: " + userData);
}
public void startPullRTCStream(InteractiveUserData userData) {
if (userData == null || TextUtils.isEmpty(userData.url)) {
Log.e(TAG, "startPull error: url is empty");
return;
}
createAlivcLivePlayer(userData);
AlivcLivePlayer alivcLivePlayer = mInteractiveLivePlayerMap.get(userData.getKey());
Log.d(TAG, "startPullRTCStream: " + userData.getKey() + ", " + userData.url + ", " + alivcLivePlayer);
if (alivcLivePlayer != null) {
alivcLivePlayer.startPlay(userData.url);
}
}
public void stopPullRTCStream(InteractiveUserData userData) {
if (userData == null) {
return;
}
String userKey = userData.getKey();
AlivcLivePlayer alivcLivePlayer = mInteractiveLivePlayerMap.get(userKey);
Log.d(TAG, "stopPullRTCStream: " + userData.getKey() + ", " + userData.url + ", " + alivcLivePlayer);
if (alivcLivePlayer != null) {
alivcLivePlayer.stopPlay();
alivcLivePlayer.destroy();
}
mInteractiveLivePlayerMap.remove(userKey);
mInteractiveUserDataMap.remove(userKey);
}
// 暂停推流
public void pausePush() {
if (isPushing()) {
mAlivcLivePusher.pause();
}
}
// 恢复推流
public void resumePush() {
if (isPushing()) {
mAlivcLivePusher.resume();
}
}
// 停止播放RTC流
public void pausePlayRTCStream(InteractiveUserData userData) {
Log.d(TAG, "pausePlayRTCStream: " + userData);
if (userData == null) {
return;
}
String userKey = userData.getKey();
InteractiveLivePlayer alivcLivePlayer = mInteractiveLivePlayerMap.get(userKey);
if (alivcLivePlayer == null) {
return;
}
alivcLivePlayer.pauseVideoPlaying();
}
// 恢复播放RTC流
public void resumePlayRTCStream(InteractiveUserData userData) {
Log.d(TAG, "resumePlayRTCStream: " + userData);
if (userData == null) {
return;
}
String userKey = userData.getKey();
InteractiveLivePlayer alivcLivePlayer = mInteractiveLivePlayerMap.get(userKey);
if (alivcLivePlayer == null) {
return;
}
alivcLivePlayer.resumeVideoPlaying();
}
// 停止播放所有RTC流
public void pausePlayRTCStream() {
for (InteractiveUserData userData : mInteractiveUserDataMap.values()) {
pausePlayRTCStream(userData);
}
}
// 恢复播放所有RTC流
public void resumePlayRTCStream() {
for (InteractiveUserData userData : mInteractiveUserDataMap.values()) {
resumePlayRTCStream(userData);
}
}
// 发送自定义消息发送SEI
public void sendCustomMessage(String text, int payload) {
if (LivePushGlobalConfig.IS_DATA_CHANNEL_MESSAGE_ENABLE) {
mAlivcLivePusher.sendDataChannelMessage(text);
} else {
mAlivcLivePusher.sendMessage(text, 0, 0, false, payload);
}
}
// 开始本地录制
public void startLocalRecord(AlivcLiveLocalRecordConfig recordConfig) {
boolean flag = false;
if (mAlivcLivePusher != null) {
flag = mAlivcLivePusher.startLocalRecord(recordConfig);
}
if (!flag) {
ToastUtils.show("start local record failed!");
}
}
// 结束本地录制
public void stopLocalRecord() {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.stopLocalRecord();
}
}
public void switchCamera() {
if (isPushing()) {
mAlivcLivePusher.switchCamera();
if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
} else {
mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
}
}
}
public void setMute(boolean isMute) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.setMute(isMute);
}
}
public void setMute(boolean isMute, AlivcLiveMuteLocalAudioMode muteLocalAudioMode) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.setMute(isMute, muteLocalAudioMode);
}
}
public void setBGMEarsBack(boolean isOpen) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.setBGMEarsBack(isOpen);
}
}
public void setAudioEffectVoiceChangeMode(AlivcLivePushAudioEffectVoiceChangeMode voiceChangeMode) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.setAudioEffectVoiceChangeMode(voiceChangeMode);
}
}
public void setVideoConfig(AlivcLivePushVideoConfig videoConfig) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.setVideoConfig(videoConfig);
}
}
public void enableSpeakerPhone(boolean enableSpeakerPhone) {
mAlivcLivePusher.enableSpeakerphone(enableSpeakerPhone);
}
public void enableAudioCapture(boolean enable) {
if (enable) {
mAlivcLivePusher.startAudioCapture(true);
} else {
mAlivcLivePusher.stopAudioCapture();
}
}
public void muteLocalCamera(boolean muteLocalCamera) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.muteLocalCamera(muteLocalCamera);
}
}
public void enableLocalCamera(boolean enable) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.enableLocalCamera(enable);
}
}
public void changeResolution(AlivcResolutionEnum resolutionEnum) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.changeResolution(resolutionEnum);
}
}
public void release() {
mSEIDelayManager.destroy();
// destroyWatermarks();
clearLiveMixTranscodingConfig();
stopPush();
destroyLivePusher();
mMultiInteractLiveMixStreamsArray.clear();
mMixInteractLiveTranscodingConfig.mixStreams = mMultiInteractLiveMixStreamsArray;
for (AlivcLivePlayer alivcLivePlayer : mInteractiveLivePlayerMap.values()) {
alivcLivePlayer.stopPlay();
alivcLivePlayer.destroy();
}
mInteractiveLivePlayerMap.clear();
mInteractiveUserDataMap.clear();
mInteractLivePushPullListener = null;
}
// 单切混的逻辑在各自场景的manager里面因为PK和连麦的混流布局不太一样。
// 更新混流、混切单
public void clearLiveMixTranscodingConfig() {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.setLiveMixTranscodingConfig(null);
}
}
// 遍历当前混流子布局信息
public AlivcLiveMixStream findMixStreamByUserData(InteractiveUserData userData) {
if (userData == null || TextUtils.isEmpty(userData.userId)) {
return null;
}
for (AlivcLiveMixStream alivcLiveMixStream : mMultiInteractLiveMixStreamsArray) {
if (userData.userId.equals(alivcLiveMixStream.userId)
&& alivcLiveMixStream.mixSourceType == InteractiveBaseUtil.covertVideoStreamType2MixSourceType(userData.videoStreamType)) {
return alivcLiveMixStream;
}
}
return null;
}
public InteractiveUserData getUserDataByKey(String key) {
if (TextUtils.isEmpty(key)) {
return null;
}
for (String userKey : mInteractiveUserDataMap.keySet()) {
if (TextUtils.equals(userKey, key)) {
return mInteractiveUserDataMap.get(userKey);
}
}
return null;
}
// 初始化美颜相关资源
private void initBeautyManager() {
if (mBeautyManager == null && LivePushGlobalConfig.ENABLE_BEAUTY) {
// v4.4.4版本-v6.1.0版本互动模式下的美颜处理逻辑参考BeautySDKType.INTERACT_QUEENInteractQueenBeautyImpl
// v6.1.0以后的版本从v6.2.0开始基础模式下的美颜和互动模式下的美颜处理逻辑保持一致QueenBeautyImpl
mBeautyManager = BeautyFactory.createBeauty(BeautySDKType.QUEEN, mContext);
// initialize in texture thread.
mBeautyManager.init();
mBeautyManager.setBeautyEnable(LivePushGlobalConfig.ENABLE_BEAUTY);
mBeautyManager.switchCameraId(mCameraId);
}
}
// 销毁美颜相关资源
private void destroyBeautyManager() {
if (mBeautyManager != null) {
mBeautyManager.release();
mBeautyManager = null;
}
}
/**
* ****************** 音频数据回调(参考示例) ****************** [START]
* <p>
* 调用建议1. 调用时机任意2. 注意清除逻辑
*/
private static final AlivcLiveAudioSource AUDIO_FRAME_OBSERVER_SOURCE = AlivcLiveAudioSource.PROCESS_CAPTURED;
private static final AlivcLiveAudioFrameObserverOperationMode AUDIO_FRAME_OBSERVER_MODE = AlivcLiveAudioFrameObserverOperationMode.READ_WRITE;
private static final AlivcLiveAudioFrameObserverUserDefinedInfoBitMask AUDIO_FRAME_OBSERVER_BIT_MASK = AlivcLiveAudioFrameObserverUserDefinedInfoBitMask.MIX_EXTERNAL_CAPTURE;
/**
* 设置音频裸数据回调
* <p>
* 注意:该接口较耗时,会影响性能,如果不需要对音频裸数据进行前处理,建议关闭
*/
public void enableAudioFrameObserver(boolean enable) {
if (mAlivcLivePusher == null) {
return;
}
if (enable) {
AlivcLiveAudioFrameObserverConfig audioFrameObserverConfig = new AlivcLiveAudioFrameObserverConfig();
audioFrameObserverConfig.mode = AUDIO_FRAME_OBSERVER_MODE;
audioFrameObserverConfig.userDefinedInfo = AUDIO_FRAME_OBSERVER_BIT_MASK;
mAlivcLivePusher.enableAudioFrameObserver(true, AUDIO_FRAME_OBSERVER_SOURCE, audioFrameObserverConfig);
mAlivcLivePusher.setLivePushAudioFrameListener(new AlivcLivePushAudioFrameListener() {
@Override
public boolean onCapturedAudioFrame(AlivcLivePushAudioFrame audioFrame) {
// 设置 enableAudioFrameObserver 接口中的 AlivcLiveAudioSource 为 CAPTURED会执行该回调
// do audio process here, and then, return true value if audio data need to be rewrited
return false;
}
@Override
public boolean onProcessCapturedAudioFrame(AlivcLivePushAudioFrame audioFrame) {
// 设置 enableAudioFrameObserver 接口中的 AlivcLiveAudioSource 为 PROCESS_CAPTURED会执行该回调
// do audio process here, and then, return true value if audio data need to be rewrited
return false;
// // 清空 byte buffer测试是否生效
// Arrays.fill(audioFrame.data, (byte) 0);
// return true;
}
@Override
public boolean onPublishAudioFrame(AlivcLivePushAudioFrame audioFrame) {
// 设置 enableAudioFrameObserver 接口中的 AlivcLiveAudioSource 为 PUB会执行该回调
// do audio process here, and then, return true value if audio data need to be rewrited
return false;
}
@Override
public boolean onPlaybackAudioFrame(AlivcLivePushAudioFrame audioFrame) {
// 设置 enableAudioFrameObserver 接口中的 AlivcLiveAudioSource 为 PLAYBACK会执行该回调
// do audio process here, and then, return true value if audio data need to be rewrited
return false;
}
@Override
public boolean onMixedAllAudioFrame(AlivcLivePushAudioFrame audioFrame) {
// 设置 enableAudioFrameObserver 接口中的 AlivcLiveAudioSource 为 MIXED_ALL会执行该回调
// 注意:设置 AlivcLiveAudioSource.MIXED_ALL 时,只能读不能写
// do audio process here, and then, return true value if audio data need to be rewrited
return false;
}
});
} else {
// please ensure the callback unregistration to prevent memory leaks
mAlivcLivePusher.enableAudioFrameObserver(false, AUDIO_FRAME_OBSERVER_SOURCE, null);
mAlivcLivePusher.setLivePushAudioFrameListener(null);
}
}
/**
* [END] ****************** 音频数据回调(参考示例) ******************
*/
/**
* ****************** 音效接口(参考示例) ****************** [START]
*/
// 音效文件 assets 路径
private static final String SOUND_EFFECT_ASSETS_PATH = "alivc_resource/sound_effect/";
// 预加载音效
public void loadAudioEffects() {
if (mAlivcLivePusher == null || mContext == null) {
return;
}
String soundEffectPath1 = mContext.getFilesDir().getPath() + File.separator + SOUND_EFFECT_ASSETS_PATH + "sound_effect_man_smile.mp3";
String soundEffectPath2 = mContext.getFilesDir().getPath() + File.separator + SOUND_EFFECT_ASSETS_PATH + "sound_effect_roar.mp3";
String soundEffectPath3 = mContext.getFilesDir().getPath() + File.separator + SOUND_EFFECT_ASSETS_PATH + "sound_effect_sinister_smile.mp3";
mAlivcLivePusher.preloadAudioEffect(1, soundEffectPath1);
mAlivcLivePusher.preloadAudioEffect(2, soundEffectPath2);
mAlivcLivePusher.preloadAudioEffect(3, soundEffectPath3);
}
// 取消预加载音效
public void unloadAudioEffects() {
if (mAlivcLivePusher == null || mContext == null) {
return;
}
mAlivcLivePusher.unloadAudioEffect(1);
mAlivcLivePusher.unloadAudioEffect(2);
mAlivcLivePusher.unloadAudioEffect(3);
}
// 播放音效
public void playAudioEffects() {
if (mAlivcLivePusher == null || mContext == null) {
return;
}
String soundEffectPath1 = mContext.getFilesDir().getPath() + File.separator + SOUND_EFFECT_ASSETS_PATH + "sound_effect_man_smile.mp3";
String soundEffectPath2 = mContext.getFilesDir().getPath() + File.separator + SOUND_EFFECT_ASSETS_PATH + "sound_effect_roar.mp3";
String soundEffectPath3 = mContext.getFilesDir().getPath() + File.separator + SOUND_EFFECT_ASSETS_PATH + "sound_effect_sinister_smile.mp3";
AlivcLiveAudioEffectConfig audioEffectConfig1 = new AlivcLiveAudioEffectConfig();
audioEffectConfig1.needPublish = false;
audioEffectConfig1.publishVolume = 0;
audioEffectConfig1.playoutVolume = 0;
AlivcLiveAudioEffectConfig audioEffectConfig2 = new AlivcLiveAudioEffectConfig();
audioEffectConfig2.needPublish = true;
audioEffectConfig2.publishVolume = 100;
audioEffectConfig2.playoutVolume = 100;
AlivcLiveAudioEffectConfig audioEffectConfig3 = new AlivcLiveAudioEffectConfig();
audioEffectConfig3.needPublish = true;
audioEffectConfig3.publishVolume = 100;
audioEffectConfig3.playoutVolume = 100;
audioEffectConfig3.loopCycles = 3;
mAlivcLivePusher.playAudioEffect(1, soundEffectPath1, audioEffectConfig1);
mAlivcLivePusher.playAudioEffect(2, soundEffectPath2, audioEffectConfig2);
mAlivcLivePusher.playAudioEffect(3, soundEffectPath3, audioEffectConfig3);
}
public void stopAudioEffect() {
if (mAlivcLivePusher == null) {
return;
}
mAlivcLivePusher.stopAudioEffect(1);
mAlivcLivePusher.stopAudioEffect(2);
mAlivcLivePusher.stopAudioEffect(3);
// mAlivcLivePusher.stopAllAudioEffects();
}
public void pauseAudioEffect() {
if (mAlivcLivePusher == null) {
return;
}
mAlivcLivePusher.pauseAudioEffect(1);
mAlivcLivePusher.pauseAudioEffect(2);
mAlivcLivePusher.pauseAudioEffect(3);
// mAlivcLivePusher.pauseAllAudioEffects();
}
public void resumeAudioEffect() {
if (mAlivcLivePusher == null) {
return;
}
mAlivcLivePusher.resumeAudioEffect(1);
mAlivcLivePusher.resumeAudioEffect(2);
mAlivcLivePusher.resumeAudioEffect(3);
// mAlivcLivePusher.resumeAllAudioEffects();
}
/**
* [END] ****************** 音效接口(参考示例) ******************
*/
/**
* ****************** 外部音视频输入/三方推流(参考示例) ****************** [START]
*/
// 新增外部音频流
public int addExternalAudioStream(AlivcLivePushExternalAudioStreamConfig externalAudioStreamConfig) {
if (mAlivcLivePusher != null) {
return mAlivcLivePusher.addExternalAudioStream(externalAudioStreamConfig);
}
return -1;
}
// 删除外部音频流
public int removeExternalAudioStream(int streamId) {
if (mAlivcLivePusher != null) {
return mAlivcLivePusher.removeExternalAudioStream(streamId);
}
return -1;
}
// 输入外部音频流数据
public int pushExternalAudioStream(int streamId, AlivcLivePushAudioFrame audioFrame) {
if (mAlivcLivePusher != null) {
return mAlivcLivePusher.pushExternalAudioStream(streamId, audioFrame);
}
return -1;
}
// 设置是否与麦克风采集音频混合
public int setMixedWithMic(boolean mixed) {
if (mAlivcLivePusher != null) {
return mAlivcLivePusher.setMixedWithMic(mixed);
}
return -1;
}
// 设置外部音频流播放音量
public int setExternalAudioStreamPlayoutVolume(int streamId, int playoutVolume) {
if (mAlivcLivePusher != null) {
return mAlivcLivePusher.setExternalAudioStreamPlayoutVolume(streamId, playoutVolume);
}
return -1;
}
// 设置外部音频流推流音量
public int setExternalAudioStreamPublishVolume(int streamId, int publishVolume) {
if (mAlivcLivePusher != null) {
return mAlivcLivePusher.setExternalAudioStreamPublishVolume(streamId, publishVolume);
}
return -1;
}
// 启用外部视频输入源
public void setExternalVideoSource(boolean enable, boolean useTexture, AlivcLivePlayVideoStreamType videoStreamType, AlivcPreviewDisplayMode previewDisplayMode) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.setExternalVideoSource(enable, useTexture, videoStreamType, previewDisplayMode);
}
}
// 外部视频输入/三方推流
public void inputStreamVideoData(byte[] data, int width, int height, int stride, int size, long pts, int rotation) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.inputStreamVideoData(data, width, height, stride, size, pts, rotation);
}
}
// 外部音频输入/三方推流
public void inputStreamAudioData(byte[] data, int size, int sampleRate, int channels, long pts) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.inputStreamAudioData(data, size, sampleRate, channels, pts);
}
}
// 输入外部视频流数据
public void pushExternalVideoFrame(AlivcLivePusherRawDataSample rawDataSample, AlivcLivePlayVideoStreamType videoStreamType) {
if (mAlivcLivePusher != null) {
mAlivcLivePusher.pushExternalVideoFrame(rawDataSample, videoStreamType);
}
}
/**
* [END] ****************** 外部音视频输入/三方推流(参考示例) ******************
*/
/**
* ****************** 视频双流(参考示例) ****************** [START]
*/
// 开始屏幕共享流
public int startScreenShare() {
if (mAlivcLivePusher != null) {
return mAlivcLivePusher.startScreenShare();
}
return -1;
}
// 停止屏幕共享流
public int stopScreenShare() {
if (mAlivcLivePusher != null) {
return mAlivcLivePusher.stopScreenShare();
}
return -1;
}
/**
* [END] ****************** 视频双流(参考示例) ******************
*/
/**
* ****************** 音频双流(参考示例) ****************** [START]
*/
// 开启第二路音频流推送
public int startDualAudioStream() {
if (mAlivcLivePusher != null) {
return mAlivcLivePusher.startLocalDualAudioStream();
}
return -1;
}
// 停止第二路音频流推送
public int stopDualAudioStream() {
if (mAlivcLivePusher != null) {
return mAlivcLivePusher.stopLocalDualAudioStream();
}
return -1;
}
/**
* [END] ****************** 音频双流(参考示例) ******************
*/
}

View File

@@ -0,0 +1,241 @@
package com.alivc.live.interactive_common;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
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.launcher.ARouter;
import com.alivc.live.commonbiz.SharedPreferenceUtils;
import com.alivc.live.commonui.utils.StatusBarUtil;
public class InteractiveAppInfoActivity extends AppCompatActivity {
public static final String FROM_EDITOR = "from_editor";
private static final int REQ_CODE_PERMISSION = 0x1111;
private ImageView mBackImageView;
private EditText mAppIdEditText;
private EditText mAppKeyEditText;
private EditText mPlayDomainEditText;
private TextView mConfirmTextView;
private ImageView mAppIdClearImageView;
private ImageView mAppKeyClearImageView;
private ImageView mPlayDomainClearImageView;
private boolean mFromEditor = false;
private int mSceneType = InteractiveConstants.SCENE_TYPE_INTERACTIVE_LIVE;
private EditText mCurrentEditText = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StatusBarUtil.translucent(this, Color.TRANSPARENT);
setContentView(R.layout.activity_interactive_app_info);
mFromEditor = getIntent().getBooleanExtra(FROM_EDITOR, false);
mSceneType = getIntent().getIntExtra(InteractiveConstants.DATA_TYPE_SCENE, InteractiveConstants.SCENE_TYPE_INTERACTIVE_LIVE);
initView();
initListener();
}
private void initView() {
mBackImageView = findViewById(R.id.iv_back);
mAppIdEditText = findViewById(R.id.et_app_id);
mAppKeyEditText = findViewById(R.id.et_app_key);
mPlayDomainEditText = findViewById(R.id.et_play_domain);
mAppIdClearImageView = findViewById(R.id.iv_app_id_clear);
mAppKeyClearImageView = findViewById(R.id.iv_app_key_clear);
mPlayDomainClearImageView = findViewById(R.id.iv_play_domain_clear);
mConfirmTextView = findViewById(R.id.tv_confirm);
}
private void initListener() {
mBackImageView.setOnClickListener((view) -> finish());
mAppIdEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
mAppIdClearImageView.setImageResource(TextUtils.isEmpty(editable.toString()) ? R.drawable.ic_scan_icon : R.drawable.ic_close);
changeConfirmTextView(checkEnable());
}
});
mAppKeyEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
mAppKeyClearImageView.setImageResource(TextUtils.isEmpty(editable.toString()) ? R.drawable.ic_scan_icon : R.drawable.ic_close);
changeConfirmTextView(checkEnable());
}
});
mPlayDomainEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
mPlayDomainClearImageView.setImageResource(TextUtils.isEmpty(editable.toString()) ? R.drawable.ic_scan_icon : R.drawable.ic_close);
changeConfirmTextView(checkEnable());
}
});
mAppIdClearImageView.setOnClickListener((view) -> checkForQrOrClear(mAppIdEditText));
mAppKeyClearImageView.setOnClickListener((view) -> checkForQrOrClear(mAppKeyEditText));
mPlayDomainClearImageView.setOnClickListener((view) -> checkForQrOrClear(mPlayDomainEditText));
mConfirmTextView.setOnClickListener((view) -> {
if (checkEnable()) {
String appId = mAppIdEditText.getText().toString();
String appKey = mAppKeyEditText.getText().toString();
String playDomain = mPlayDomainEditText.getText().toString();
SharedPreferenceUtils.setAppInfo(getApplicationContext(), appId, appKey, playDomain);
if (!mFromEditor) {
ARouter.getInstance()
.build("/interactiveCommon/liveInput")
.withInt(InteractiveConstants.DATA_TYPE_SCENE, mSceneType)
.navigation();
}
finish();
}
});
mAppIdEditText.setText(SharedPreferenceUtils.getAppId(getApplicationContext().getApplicationContext()));
mAppKeyEditText.setText(SharedPreferenceUtils.getAppKey(getApplicationContext().getApplicationContext()));
mPlayDomainEditText.setText(SharedPreferenceUtils.getPlayDomain(getApplicationContext().getApplicationContext()));
}
private void checkForQrOrClear(EditText editText) {
String content = editText.getText().toString().trim();
if (TextUtils.isEmpty(content)) {
mCurrentEditText = editText;
startQr();
} else {
editText.setText("");
}
}
private void changeConfirmTextView(boolean enable) {
if (enable) {
mConfirmTextView.setBackground(getResources().getDrawable(R.drawable.shape_pysh_btn_bg));
} else {
mConfirmTextView.setBackground(getResources().getDrawable(R.drawable.shape_rect_blue));
}
}
private boolean checkEnable() {
String appId = mAppIdEditText.getText().toString();
String appKey = mAppKeyEditText.getText().toString();
String playDomain = mPlayDomainEditText.getText().toString();
return !TextUtils.isEmpty(appId) && !TextUtils.isEmpty(appKey) && !TextUtils.isEmpty(playDomain);
}
private void startQr() {
if (ContextCompat.checkSelfPermission(InteractiveAppInfoActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// Do not have the permission of camera, request it.
ActivityCompat.requestPermissions(InteractiveAppInfoActivity.this, new String[]{Manifest.permission.CAMERA}, REQ_CODE_PERMISSION);
} else {
// Have gotten the permission
startCaptureActivityForResult();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQ_CODE_PERMISSION: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// User agree the permission
startCaptureActivityForResult();
} else {
// User disagree the permission
Toast.makeText(this, "You must agree the camera permission request before you use the code scan function", Toast.LENGTH_LONG).show();
}
}
break;
default:
break;
}
}
@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 (mCurrentEditText != null) {
mCurrentEditText.setText(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
break;
case RESULT_CANCELED:
if (data != null && mCurrentEditText != null) {
// for some reason camera is not working correctly
mCurrentEditText.setText(data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT));
}
break;
default:
break;
}
break;
default:
break;
}
}
private void startCaptureActivityForResult() {
Intent intent = new Intent(InteractiveAppInfoActivity.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,57 @@
package com.alivc.live.interactive_common;
import com.alivc.live.player.annotations.AlivcLivePlayVideoStreamType;
import com.alivc.live.pusher.AlivcLiveMixSourceType;
import java.util.Random;
/**
* @author keria
* @date 2023/10/30
* @brief
*/
public class InteractiveBaseUtil {
private InteractiveBaseUtil() {
}
/**
* 生成随机房间号和用户id
*
* @param sceneType 场景直播连麦场景随机数0-100PK场景随机数100-999以此区分
* @param listener 回调结果
*/
public static void generateRandomId(int sceneType, OnRandomIdListener listener) {
Random random = new Random();
int randomNumber;
String channelId;
String userId;
if (sceneType == InteractiveConstants.SCENE_TYPE_INTERACTIVE_LIVE) {
randomNumber = random.nextInt(99) + 1;
String randomNumberStr = String.valueOf(randomNumber);
userId = randomNumberStr; // 互动直播的场景用户ID随机数范围 0-99
channelId = randomNumberStr; // 互动直播的场景房间ID随机数范围 0-99
} else {
randomNumber = random.nextInt(900) + 100; // 非互动直播的场景,随机数范围 0-999
String randomNumberStr = String.valueOf(randomNumber);
channelId = randomNumberStr;// 设置房间ID
userId = randomNumberStr;// 设置用户ID与房间ID相同
}
if (listener != null) {
listener.onResult(channelId, userId);
}
}
public static AlivcLiveMixSourceType covertVideoStreamType2MixSourceType(AlivcLivePlayVideoStreamType videoStreamType) {
if (videoStreamType == AlivcLivePlayVideoStreamType.STREAM_SCREEN) {
return AlivcLiveMixSourceType.SOURCE_SCREEN;
}
return AlivcLiveMixSourceType.SOURCE_CAMERA;
}
public interface OnRandomIdListener {
void onResult(String channelId, String userId);
}
}

View File

@@ -0,0 +1,17 @@
package com.alivc.live.interactive_common;
/**
* @author keria
* @date 2024/5/29
* @brief 互动直播常量类
*/
public @interface InteractiveConstants {
String DATA_TYPE_INTERACTIVE_USER_DATA = "user_data";
String DATA_TYPE_IS_ANCHOR = "IS_ANCHOR";
String DATA_TYPE_SCENE = "SCENE";
String KEY_TYPE_SCENE_MULTI_PK_16IN = "scene_multi_pk_16in";
int SCENE_TYPE_INTERACTIVE_LIVE = 0;
int SCENE_TYPE_PK_LIVE = 1;
}

View File

@@ -0,0 +1,294 @@
package com.alivc.live.interactive_common;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.acker.simplezxing.activity.CaptureActivity;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alibaba.android.arouter.launcher.ARouter;
import com.alivc.live.commonbiz.backdoor.BackDoorInstance;
import com.alivc.live.commonbiz.test.AliLiveStreamURLUtil;
import com.alivc.live.commonui.utils.StatusBarUtil;
import com.alivc.live.commonutils.TextFormatUtil;
import com.alivc.live.commonutils.ToastUtils;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.interactive_common.utils.LivePushGlobalConfig;
import com.alivc.live.interactive_common.widget.InteractLiveRadioButton;
import java.util.HashMap;
import java.util.regex.Pattern;
/**
* @author keria
* @date 2024/5/29
* @brief 互动直播房间号/用户ID输入页面
*/
@Route(path = "/interactiveCommon/liveInput")
public class InteractiveInputActivity extends AppCompatActivity {
private EditText mUserIdEdt;
private EditText mRoomIdEdt;
private ImageView mUserIdClearIv;
private ImageView mRoomIdClearIv;
private TextView mURLEntranceTv;
private TextView mInteractiveURLTv;
private TextView mSettingTv;
private TextView mConfirmTv;
private ImageView mBackIv;
// 直播连麦场景下的主播/观众角色选取
private InteractLiveRadioButton mAnchorRb;
private InteractLiveRadioButton mAudienceRb;
// 场景类型PK直播 or 连麦直播
private int mSceneType = InteractiveConstants.SCENE_TYPE_INTERACTIVE_LIVE;
private boolean isAnchor = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StatusBarUtil.translucent(this, Color.TRANSPARENT);
setContentView(R.layout.activity_interactive_input);
// 获取场景类型
mSceneType = getIntent().getIntExtra(InteractiveConstants.DATA_TYPE_SCENE, InteractiveConstants.SCENE_TYPE_INTERACTIVE_LIVE);
initView();
initListener();
}
private void initView() {
mUserIdEdt = findViewById(R.id.et_user_id);
mRoomIdEdt = findViewById(R.id.et_room_id);
mUserIdClearIv = findViewById(R.id.iv_user_id_clear);
mRoomIdClearIv = findViewById(R.id.iv_room_id_clear);
mURLEntranceTv = findViewById(R.id.tv_url_entrance);
mInteractiveURLTv = findViewById(R.id.tv_interactive_url);
// Update page title
TextView titleTv = findViewById(R.id.tv_title);
titleTv.setText(mSceneType == InteractiveConstants.SCENE_TYPE_PK_LIVE ? getString(R.string.pk_live) : getString(R.string.interact_live));
mSettingTv = findViewById(R.id.tv_setting);
mBackIv = findViewById(R.id.iv_back);
mConfirmTv = findViewById(R.id.tv_confirm);
View roleContainer = findViewById(R.id.role_container);
roleContainer.setVisibility(mSceneType == InteractiveConstants.SCENE_TYPE_PK_LIVE ? View.GONE : View.VISIBLE);
mAnchorRb = findViewById(R.id.radio_button_anchor);
mAnchorRb.setChecked(true);
mAudienceRb = findViewById(R.id.radio_button_audience);
}
private void initListener() {
InteractLiveAppInfoFragment interactLiveAppInfoFragment = (InteractLiveAppInfoFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_app_info);
if (interactLiveAppInfoFragment != null) {
interactLiveAppInfoFragment.setOnEditClickListener(new InteractLiveAppInfoFragment.OnEditClickListener() {
@Override
public void onLongClickAppInfoForDebug() {
InteractiveBaseUtil.generateRandomId(mSceneType, (channelId, userId) -> {
if (TextUtils.isEmpty(mRoomIdEdt.getText())) {
mRoomIdEdt.setText(channelId);
}
if (TextUtils.isEmpty(mUserIdEdt.getText())) {
mUserIdEdt.setText(userId);
}
});
}
@Override
public void onEditClickListener() {
Intent intent = new Intent(InteractiveInputActivity.this, InteractiveAppInfoActivity.class);
intent.putExtra(InteractiveAppInfoActivity.FROM_EDITOR, true);
intent.putExtra(InteractiveConstants.DATA_TYPE_SCENE, mSceneType);
startActivity(intent);
}
});
}
mBackIv.setOnClickListener(view -> finish());
mURLEntranceTv.setOnClickListener(v -> {
startCaptureActivityForResult();
});
mSettingTv.setOnClickListener(view -> {
Intent intent = new Intent(this, InteractiveSettingActivity.class);
startActivity(intent);
});
mUserIdEdt.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
changeConfirmTextView(checkEnable());
}
});
mRoomIdEdt.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
changeConfirmTextView(checkEnable());
}
});
mAnchorRb.setOnClickListener(view -> {
isAnchor = true;
mAnchorRb.setChecked(true);
mAudienceRb.setChecked(false);
});
mAudienceRb.setOnClickListener(view -> {
isAnchor = false;
mAnchorRb.setChecked(false);
mAudienceRb.setChecked(true);
});
mUserIdClearIv.setOnClickListener(view -> mUserIdEdt.setText(""));
mRoomIdClearIv.setOnClickListener(view -> mRoomIdEdt.setText(""));
mConfirmTv.setOnClickListener(view -> {
if (checkEnable()) {
startInteractiveActivity();
}
});
}
private void changeConfirmTextView(boolean enable) {
if (enable) {
mConfirmTv.setBackground(getResources().getDrawable(R.drawable.shape_pysh_btn_bg));
} else {
mConfirmTv.setBackground(getResources().getDrawable(R.drawable.shape_rect_blue));
}
}
private boolean checkEnable() {
String url = mInteractiveURLTv.getText().toString();
if (!TextUtils.isEmpty(url)) {
return true;
}
String userId = mUserIdEdt.getText().toString();
String roomId = mRoomIdEdt.getText().toString();
return !TextUtils.isEmpty(userId) && !TextUtils.isEmpty(roomId) && Pattern.matches(TextFormatUtil.REGULAR, userId) && Pattern.matches(TextFormatUtil.REGULAR, roomId);
}
private void startInteractiveActivity() {
String schema;
if (mSceneType == InteractiveConstants.SCENE_TYPE_PK_LIVE) {
schema = LivePushGlobalConfig.IS_MULTI_INTERACT
? "/interactivePK/multiPKLive"
: "/interactivePK/pkLive";
} else {
schema = LivePushGlobalConfig.IS_MULTI_INTERACT
? "/interactiveLive/multiInteractLive"
: "/interactiveLive/interactLive";
}
ARouter.getInstance()
.build(schema)
.withSerializable(InteractiveConstants.DATA_TYPE_INTERACTIVE_USER_DATA, generateUserData())
.withBoolean(InteractiveConstants.DATA_TYPE_IS_ANCHOR, isAnchor)
.withBoolean(InteractiveConstants.KEY_TYPE_SCENE_MULTI_PK_16IN, BackDoorInstance.getInstance().isUseMultiPK16IN())
.navigation();
}
private void startCaptureActivityForResult() {
Intent intent = new Intent(InteractiveInputActivity.this, CaptureActivity.class);
startActivityForResult(intent, CaptureActivity.REQ_CODE);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CaptureActivity.REQ_CODE) {
if (resultCode == RESULT_OK) {
String pushUrl = data.getStringExtra(CaptureActivity.EXTRA_SCAN_RESULT);
if (TextUtils.isEmpty(pushUrl)) {
return;
}
mInteractiveURLTv.setText(pushUrl);
clearInputUserInfo(true);
changeConfirmTextView(checkEnable());
checkPushURLSDKAppID(pushUrl);
}
}
}
// 清除输入的用户信息
private void clearInputUserInfo(boolean useUrl) {
if (useUrl) {
mUserIdEdt.setText("");
mRoomIdEdt.setText("");
} else {
mInteractiveURLTv.setText("");
}
}
// 如果当前以URL方式输入的 sdkAppId 和 demo 当前环境使用的 sdkAppId 不一致,则提示 toast以免业务异常
private void checkPushURLSDKAppID(String pushURL) {
if (TextUtils.isEmpty(pushURL)) {
return;
}
HashMap<String, String> params = AliLiveStreamURLUtil.parseUrl(pushURL);
String sdkAppId = params.get(AliLiveStreamURLUtil.SDK_APP_ID);
if (!TextUtils.equals(sdkAppId, AliLiveStreamURLUtil.APP_ID)) {
ToastUtils.show(getString(R.string.tips_interactive_url_use_different_sdkappid));
}
}
/**
* 生成 user data里面存有房间号用户id实际推流地址等信息
*
* @return user data
*/
private InteractiveUserData generateUserData() {
String channelId = mRoomIdEdt.getText().toString();
String userId = mUserIdEdt.getText().toString();
String pushURL = mInteractiveURLTv.getText().toString();
InteractiveUserData userData = new InteractiveUserData();
if (!TextUtils.isEmpty(channelId) && !TextUtils.isEmpty(userId)) {
userData.userId = userId;
userData.channelId = channelId;
userData.url = AliLiveStreamURLUtil.generateInteractivePushUrl(channelId, userId);
} else if (!TextUtils.isEmpty(pushURL)) {
HashMap<String, String> params = AliLiveStreamURLUtil.parseUrl(pushURL);
userData.channelId = AliLiveStreamURLUtil.parseURLStreamName(params);
userData.userId = params.get(AliLiveStreamURLUtil.USER_ID);
userData.url = pushURL;
}
return userData;
}
}

View File

@@ -0,0 +1,70 @@
package com.alivc.live.interactive_common;
/**
* @author keria
* @date 2023/10/27
* @brief DEMO互动模式业务类型
*/
public enum InteractiveMode {
/**
* 1v1连麦
*/
INTERACTIVE(0x10),
/**
* 多人连麦
*/
MULTI_INTERACTIVE(0x11),
/**
* 1v1 PK
*/
PK(0x20),
/**
* 多人PK
*/
MULTI_PK(0x21),
/**
* 1v1推拉裸流
*/
BARE_STREAM(0x30),
/**
* 多人推拉裸流
*/
MULTI_BARE_STREAM(0x31),
;
private final int mode;
InteractiveMode(int mode) {
this.mode = mode;
}
/**
* 是否是直播连麦(先推后拉)
*
* @param interactiveMode 互动模式业务类型
* @return true->是false->否
*/
public static boolean isInteractive(InteractiveMode interactiveMode) {
return interactiveMode == INTERACTIVE
|| interactiveMode == MULTI_INTERACTIVE
|| interactiveMode == PK
|| interactiveMode == MULTI_PK;
}
/**
* 是否是推拉裸流(直推直拉)
*
* @param interactiveMode 互动模式业务类型
* @return true->是false->否
*/
public static boolean isBareStream(InteractiveMode interactiveMode) {
return interactiveMode == BARE_STREAM
|| interactiveMode == MULTI_BARE_STREAM;
}
}

View File

@@ -0,0 +1,350 @@
package com.alivc.live.interactive_common;
import static com.alivc.live.interactive_common.utils.LivePushGlobalConfig.mAlivcLivePushConfig;
import android.app.DownloadManager;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import com.alivc.live.annotations.AlivcLiveCameraCaptureOutputPreference;
import com.alivc.live.commonbiz.ResourcesDownload;
import com.alivc.live.commonbiz.SharedPreferenceUtils;
import com.alivc.live.commonui.configview.LivePushSettingView;
import com.alivc.live.commonui.utils.StatusBarUtil;
import com.alivc.live.commonui.widgets.AVLiveLoadingDialog;
import com.alivc.live.commonutils.DownloadUtil;
import com.alivc.live.commonutils.ToastUtils;
import com.alivc.live.interactive_common.utils.LivePushGlobalConfig;
import com.alivc.live.pusher.AlivcAudioChannelEnum;
import com.alivc.live.pusher.AlivcAudioSampleRateEnum;
import com.alivc.live.pusher.AlivcAudioSceneModeEnum;
import com.alivc.live.pusher.AlivcEncodeModeEnum;
import com.alivc.live.pusher.AlivcImageFormat;
import com.alivc.live.pusher.AlivcLivePushCameraTypeEnum;
import com.alivc.live.pusher.AlivcPreviewOrientationEnum;
import com.alivc.live.pusher.AlivcResolutionEnum;
import com.alivc.live.pusher.AlivcSoundFormat;
import java.io.File;
public class InteractiveSettingActivity extends AppCompatActivity {
private View mTabArgsLayout;
private View mTabActionLayout;
private View mTabArgsView;
private View mTabActionView;
private ImageView mBackImageView;
private Switch mMultiInteractSwitch;
private Switch mH5CompatibleSwitch;
private Switch mDataChannelSwitch;
private Switch mEarbackOpenWithoutHeadsetSwitch;
private TextView mCameraCaptureOutputPreferenceTv;
private Button mCommitButton;
private AVLiveLoadingDialog mLoadingDialog;
private LivePushSettingView mLivePushSettingView;
private AlivcResolutionEnum mCurrentResolution;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StatusBarUtil.translucent(this, Color.TRANSPARENT);
setContentView(R.layout.activity_interactive_setting);
initView();
initData();
initListener();
}
private void initView() {
mBackImageView = findViewById(R.id.iv_back);
mCommitButton = findViewById(R.id.btn_commit);
mMultiInteractSwitch = findViewById(R.id.multi_interaction_control);
mMultiInteractSwitch.setChecked(LivePushGlobalConfig.IS_MULTI_INTERACT);
mH5CompatibleSwitch = findViewById(R.id.h5_compatible_control);
mH5CompatibleSwitch.setChecked(LivePushGlobalConfig.IS_H5_COMPATIBLE);
mDataChannelSwitch = findViewById(R.id.data_channel_control);
mDataChannelSwitch.setChecked(LivePushGlobalConfig.IS_DATA_CHANNEL_MESSAGE_ENABLE);
mEarbackOpenWithoutHeadsetSwitch = findViewById(R.id.earback_open_without_headset_control);
mEarbackOpenWithoutHeadsetSwitch.setChecked(LivePushGlobalConfig.IS_EARBACK_OPEN_WITHOUT_HEADSET);
mCameraCaptureOutputPreferenceTv = findViewById(R.id.setting_camera_capture_output_preference);
String cameraCaptureOutputPreferenceText = getTextFromCameraCaptureOutputPreference(LivePushGlobalConfig.CAMERA_CAPTURE_OUTPUT_PREFERENCE);
mCameraCaptureOutputPreferenceTv.setText(cameraCaptureOutputPreferenceText);
mTabArgsLayout = findViewById(R.id.tab_args_layout);
mTabActionLayout = findViewById(R.id.tab_action_layout);
mTabArgsView = findViewById(R.id.tab_args_view);
mTabActionView = findViewById(R.id.tab_action_view);
mLivePushSettingView = findViewById(R.id.setting_view);
}
private void initData() {
mLivePushSettingView.setDefaultConfig(mAlivcLivePushConfig, LivePushGlobalConfig.ENABLE_BEAUTY, LivePushGlobalConfig.ENABLE_LOCAL_LOG, LivePushGlobalConfig.ENABLE_WATER_MARK);
}
private void initListener() {
mLivePushSettingView.bitrateControl.observe(this, isChecked -> {
mAlivcLivePushConfig.setEnableBitrateControl(isChecked);
});
mLivePushSettingView.targetVideoBitrate.observe(this, bitrate -> {
SharedPreferenceUtils.setHintTargetBit(getApplicationContext(), bitrate);
mAlivcLivePushConfig.setTargetVideoBitrate(bitrate);
});
mLivePushSettingView.minVideoBitrate.observe(this, bitrate -> {
SharedPreferenceUtils.setHintMinBit(getApplicationContext(), bitrate);
mAlivcLivePushConfig.setMinVideoBitrate(bitrate);
});
mLivePushSettingView.initialVideoBitrate.observe(this, bitrate -> {
SharedPreferenceUtils.setHintMinBit(getApplicationContext(), bitrate);
mAlivcLivePushConfig.setInitialVideoBitrate(bitrate);
});
mLivePushSettingView.audioBitrate.observe(this, bitrate -> {
SharedPreferenceUtils.setHintMinBit(getApplicationContext(), bitrate);
mAlivcLivePushConfig.setAudioBitRate(bitrate);
});
mLivePushSettingView.variableResolution.observe(this, isChecked -> {
mAlivcLivePushConfig.setEnableAutoResolution(isChecked);
});
mLivePushSettingView.minFps.observe(this, minFps -> {
mAlivcLivePushConfig.setMinFps(minFps);
});
mLivePushSettingView.audioSampleRate.observe(this, sampleRate -> {
mAlivcLivePushConfig.setAudioSampleRate(sampleRate);
});
mLivePushSettingView.gop.observe(this, gop -> {
mAlivcLivePushConfig.setVideoEncodeGop(gop);
});
mLivePushSettingView.fps.observe(this, fps -> {
mAlivcLivePushConfig.setFps(fps);
});
mLivePushSettingView.videoHardwareDecode.observe(this, isChecked -> {
mAlivcLivePushConfig.setVideoEncodeMode(isChecked ? AlivcEncodeModeEnum.Encode_MODE_HARD : AlivcEncodeModeEnum.Encode_MODE_SOFT);
});
mLivePushSettingView.audioHardwareDecode.observe(this, isChecked -> {
mAlivcLivePushConfig.setAudioEncodeMode(isChecked ? AlivcEncodeModeEnum.Encode_MODE_HARD : AlivcEncodeModeEnum.Encode_MODE_SOFT);
});
mLivePushSettingView.pushMirror.observe(this, isChecked -> {
mAlivcLivePushConfig.setPushMirror(isChecked);
});
mLivePushSettingView.previewMirror.observe(this, isChecked -> {
mAlivcLivePushConfig.setPreviewMirror(isChecked);
});
mLivePushSettingView.enableFrontCamera.observe(this, isChecked -> {
mAlivcLivePushConfig.setCameraType(isChecked ? AlivcLivePushCameraTypeEnum.CAMERA_TYPE_FRONT : AlivcLivePushCameraTypeEnum.CAMERA_TYPE_BACK);
// mCameraId = (isChecked ? AlivcLivePushCameraTypeEnum.CAMERA_TYPE_FRONT.getCameraId() : AlivcLivePushCameraTypeEnum.CAMERA_TYPE_BACK.getCameraId());
});
mLivePushSettingView.autoFocus.observe(this, isChecked -> {
mAlivcLivePushConfig.setAutoFocus(isChecked);
});
mLivePushSettingView.enableBeauty.observe(this, isChecked -> {
LivePushGlobalConfig.ENABLE_BEAUTY = isChecked;
});
mLivePushSettingView.videoOnly.observe(this, isChecked -> {
mAlivcLivePushConfig.setVideoOnly(isChecked);
});
mLivePushSettingView.audioOnly.observe(this, isChecked -> {
mAlivcLivePushConfig.setAudioOnly(isChecked);
});
mLivePushSettingView.pauseImage.observe(this, isChecked -> {
if (!isChecked) {
mAlivcLivePushConfig.setPausePushImage("");
} else {
if (mAlivcLivePushConfig.getPreviewOrientation() == AlivcPreviewOrientationEnum.ORIENTATION_LANDSCAPE_HOME_LEFT.getOrientation() || mAlivcLivePushConfig.getPreviewOrientation() == AlivcPreviewOrientationEnum.ORIENTATION_LANDSCAPE_HOME_RIGHT.getOrientation()) {
mAlivcLivePushConfig.setPausePushImage(getFilesDir().getPath() + File.separator + "alivc_resource/background_push_land.png");
} else {
mAlivcLivePushConfig.setPausePushImage(getFilesDir().getPath() + File.separator + "alivc_resource/background_push.png");
}
}
});
mLivePushSettingView.netWorkImage.observe(this, isChecked -> {
if (!isChecked) {
mAlivcLivePushConfig.setNetworkPoorPushImage("");
} else {
if (mAlivcLivePushConfig.getPreviewOrientation() == AlivcPreviewOrientationEnum.ORIENTATION_LANDSCAPE_HOME_LEFT.getOrientation() || mAlivcLivePushConfig.getPreviewOrientation() == AlivcPreviewOrientationEnum.ORIENTATION_LANDSCAPE_HOME_RIGHT.getOrientation()) {
mAlivcLivePushConfig.setNetworkPoorPushImage(getFilesDir().getPath() + File.separator + "alivc_resource/poor_network_land.png");
} else {
mAlivcLivePushConfig.setNetworkPoorPushImage(getFilesDir().getPath() + File.separator + "alivc_resource/poor_network.png");
}
}
});
mLivePushSettingView.musicMode.observe(this, isChecked -> {
mAlivcLivePushConfig.setAudioSceneMode(isChecked ? AlivcAudioSceneModeEnum.AUDIO_SCENE_MUSIC_MODE : AlivcAudioSceneModeEnum.AUDIO_SCENE_DEFAULT_MODE);
});
mLivePushSettingView.extern.observe(this, isChecked -> {
mAlivcLivePushConfig.setExternMainStream(isChecked, AlivcImageFormat.IMAGE_FORMAT_YUVNV12, AlivcSoundFormat.SOUND_FORMAT_S16);
mAlivcLivePushConfig.setAudioChannels(AlivcAudioChannelEnum.AUDIO_CHANNEL_ONE);
mAlivcLivePushConfig.setAudioSampleRate(AlivcAudioSampleRateEnum.AUDIO_SAMPLE_RATE_44100);
if (isChecked) {
startDownloadYUV();
}
});
mLivePushSettingView.localLog.observe(this, isChecked -> {
LivePushGlobalConfig.ENABLE_LOCAL_LOG = isChecked;
});
mLivePushSettingView.previewDisplayMode.observe(this, previewDisplayMode -> {
mAlivcLivePushConfig.setPreviewDisplayMode(previewDisplayMode);
});
mLivePushSettingView.cameraCaptureOutputPreference.observe(this, preference -> {
mAlivcLivePushConfig.setCameraCaptureOutputPreference(preference);
});
mLivePushSettingView.audioChannel.observe(this, audioChannel -> {
mAlivcLivePushConfig.setAudioChannels(audioChannel);
});
mLivePushSettingView.audioProfile.observe(this, audioProfile -> {
mAlivcLivePushConfig.setAudioProfile(audioProfile);
});
mLivePushSettingView.videoEncodeType.observe(this, videoEncodeType -> {
mAlivcLivePushConfig.setVideoEncodeType(videoEncodeType);
});
mLivePushSettingView.bFrame.observe(this, bFrame -> {
mAlivcLivePushConfig.setBFrames(bFrame);
});
mLivePushSettingView.previewOrientation.observe(this, orientation -> {
mAlivcLivePushConfig.setPreviewOrientation(orientation);
});
mLivePushSettingView.pauseImagePath.observe(this, path -> {
if (mAlivcLivePushConfig.getPausePushImage() == null || mAlivcLivePushConfig.getPausePushImage().equals("")) {
mAlivcLivePushConfig.setPausePushImage(path);
}
});
mLivePushSettingView.netWorkImagePath.observe(this, path -> {
if (mAlivcLivePushConfig.getNetworkPoorPushImage() != null && !mAlivcLivePushConfig.getNetworkPoorPushImage().equals("")) {
mAlivcLivePushConfig.setNetworkPoorPushImage(path);
}
});
mLivePushSettingView.qualityMode.observe(this, quality -> {
mAlivcLivePushConfig.setQualityMode(quality);
});
mLivePushSettingView.resolution.observe(this, resolution -> {
this.mCurrentResolution = resolution;
if (resolution != AlivcResolutionEnum.RESOLUTION_SELF_DEFINE) {
mAlivcLivePushConfig.setResolution(resolution);
}
});
mTabArgsLayout.setOnClickListener(view -> {
mTabArgsView.setVisibility(View.VISIBLE);
mTabActionView.setVisibility(View.INVISIBLE);
mLivePushSettingView.showArgsContent(true);
});
mTabActionLayout.setOnClickListener(view -> {
mTabActionView.setVisibility(View.VISIBLE);
mTabArgsView.setVisibility(View.INVISIBLE);
mLivePushSettingView.showArgsContent(false);
});
mBackImageView.setOnClickListener(view -> {
finish();
});
mCommitButton.setOnClickListener(view -> {
if (mCurrentResolution == AlivcResolutionEnum.RESOLUTION_SELF_DEFINE) {
AlivcResolutionEnum.RESOLUTION_SELF_DEFINE.setSelfDefineResolution(mLivePushSettingView.getSelfDefineResolutionWidth(), mLivePushSettingView.getSelfDefineResolutionHeight());
mAlivcLivePushConfig.setResolution(AlivcResolutionEnum.RESOLUTION_SELF_DEFINE);
}
mAlivcLivePushConfig.setConnectRetryCount(mLivePushSettingView.getRetryCount());
mAlivcLivePushConfig.setConnectRetryInterval(mLivePushSettingView.getRetryInterval());
finish();
});
mMultiInteractSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
LivePushGlobalConfig.IS_MULTI_INTERACT = isChecked;
}
});
mH5CompatibleSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
LivePushGlobalConfig.IS_H5_COMPATIBLE = isChecked;
}
});
mDataChannelSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
LivePushGlobalConfig.IS_DATA_CHANNEL_MESSAGE_ENABLE = isChecked;
}
});
mEarbackOpenWithoutHeadsetSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
LivePushGlobalConfig.IS_EARBACK_OPEN_WITHOUT_HEADSET = isChecked;
}
});
}
private void showProgressDialog(@StringRes int tipsResId) {
if (null == mLoadingDialog) {
mLoadingDialog = new AVLiveLoadingDialog(this).tip(getString(tipsResId));
}
if (!mLoadingDialog.isShowing()) {
mLoadingDialog.show();
}
}
private void hideProgressDialog() {
if (mLoadingDialog != null && mLoadingDialog.isShowing()) {
mLoadingDialog.dismiss();
mLoadingDialog = null;
}
}
private void startDownloadYUV() {
long mCaptureDownloadId = ResourcesDownload.downloadCaptureYUV(this, new DownloadUtil.OnDownloadListener() {
@Override
public void onDownloadSuccess(long downloadId) {
hideProgressDialog();
ToastUtils.show("Download Success");
}
@Override
public void onDownloadProgress(long downloadId, double percent) {
}
@Override
public void onDownloadError(long downloadId, int errorCode, String errorMsg) {
hideProgressDialog();
ToastUtils.show(errorMsg);
if (errorCode != DownloadManager.ERROR_FILE_ALREADY_EXISTS) {
mLivePushSettingView.externDownloadError();
}
}
});
showProgressDialog(R.string.waiting_download_video_resources);
}
private String getTextFromCameraCaptureOutputPreference(AlivcLiveCameraCaptureOutputPreference cameraCaptureOutputPreference) {
if (cameraCaptureOutputPreference == AlivcLiveCameraCaptureOutputPreference.AUTO) {
return getString(R.string.camera_capture_output_preference_auto);
} else if (cameraCaptureOutputPreference == AlivcLiveCameraCaptureOutputPreference.PERFORMANCE) {
return getString(R.string.camera_capture_output_preference_performance);
} else if (cameraCaptureOutputPreference == AlivcLiveCameraCaptureOutputPreference.PREVIEW) {
return getString(R.string.camera_capture_output_preference_preview);
} else {
return getString(R.string.camera_capture_output_preference_preview);
}
}
}

View File

@@ -0,0 +1,141 @@
package com.alivc.live.interactive_common.bean;
import android.content.Context;
import android.util.Log;
import com.alivc.live.annotations.AlivcLiveMode;
import com.alivc.live.annotations.AlivcLiveNetworkQuality;
import com.alivc.live.commonutils.ToastUtils;
import com.alivc.live.interactive_common.listener.InteractLivePushPullListener;
import com.alivc.live.player.AlivcLivePlayInfoListener;
import com.alivc.live.player.AlivcLivePlayerImpl;
import com.alivc.live.player.AlivcLivePlayerStatsInfo;
import com.alivc.live.player.annotations.AlivcLivePlayError;
/**
* AlivcLivePlayer 封装类,用于直播连麦拉流
*/
public class InteractiveLivePlayer extends AlivcLivePlayerImpl {
private static final String TAG = InteractiveLivePlayer.class.getSimpleName();
private InteractLivePushPullListener mListener;
private boolean mIsPulling = false;
private final AlivcLivePlayInfoListener mAlivcLivePlayInfoListener = new AlivcLivePlayInfoListener() {
@Override
public void onPlayStarted() {
Log.i(TAG, "onPlayStarted: " + mPullUserData);
mIsPulling = true;
if (mListener != null) {
mListener.onPullSuccess(mPullUserData);
}
}
@Override
public void onPlayStopped() {
Log.i(TAG, "onPlayStopped: " + mPullUserData);
notifyPullStop();
}
@Override
public void onFirstVideoFrameDrawn() {
}
@Override
public void onNetworkQualityChanged(AlivcLiveNetworkQuality upQuality, AlivcLiveNetworkQuality downQuality) {
Log.w(TAG, "onNetworkQualityChanged: " + upQuality + ", " + downQuality);
}
@Override
public void onReceiveSEIMessage(int payload, byte[] data) {
//Log.d(TAG, "onReceiveSEIMessage: " + payload + ", " + new String(data, StandardCharsets.UTF_8));
if (mListener != null) {
mListener.onReceiveSEIMessage(payload, data);
}
}
@Override
public void onReceiveDataChannelMessage(byte[] data) {
super.onReceiveDataChannelMessage(data);
//Log.d(TAG, "onReceiveDataChannelMessage: " + new String(data, StandardCharsets.UTF_8));
}
@Override
public void onPlayoutVolumeUpdate(int volume, boolean isSpeaking) {
// 音频音量(仅互动模式下生效需设置AlivcLivePusher#enableAudioVolumeIndication接口)
// Log.d(TAG, "onPlayoutVolumeUpdate: " + volume + ", " + isSpeaking);
}
@Override
public void onAudioMuted(boolean mute) {
ToastUtils.show("onAudioMuted: " + mute);
}
@Override
public void onVideoMuted(boolean mute) {
ToastUtils.show("onVideoMuted: " + mute);
}
@Override
public void onVideoEnabled(boolean enable) {
ToastUtils.show("onVideoEnabled: " + enable);
if (mListener != null) {
mListener.onVideoEnabled(enable);
}
}
@Override
public void onVideoResolutionChanged(int width, int height) {
}
@Override
public void onError(AlivcLivePlayError alivcLivePlayError, String s) {
Log.e(TAG, "onError: " + mPullUserData + ", " + alivcLivePlayError + ", " + s);
mIsPulling = false;
if (mListener != null) {
mListener.onPullError(mPullUserData, alivcLivePlayError, s);
}
}
@Override
public void onPlayerStatistics(AlivcLivePlayerStatsInfo statsInfo) {
//Log.i(TAG, "onPlayerStatistics: " + statsInfo);
}
};
private InteractiveUserData mPullUserData;
public InteractiveLivePlayer(Context context, AlivcLiveMode mode) {
super(context, mode);
setPlayInfoListener(mAlivcLivePlayInfoListener);
}
public void setMultiInteractPlayInfoListener(InteractLivePushPullListener listener) {
this.mListener = listener;
}
public boolean isPulling() {
return mIsPulling;
}
public void setPullUserData(InteractiveUserData userData) {
this.mPullUserData = userData;
}
// ugly codes: fix the issue of async callbacks not being executed in stopPlay after executing destroy immediately
@Override
public void destroy() {
notifyPullStop();
setPlayInfoListener(null);
mListener = null;
super.destroy();
}
private void notifyPullStop() {
mIsPulling = false;
if (mListener != null) {
mListener.onPullStop(mPullUserData);
}
}
}

View File

@@ -0,0 +1,42 @@
package com.alivc.live.interactive_common.bean;
import android.text.TextUtils;
import android.util.Log;
import com.alivc.live.player.annotations.AlivcLivePlayAudioStreamType;
import com.alivc.live.player.annotations.AlivcLivePlayVideoStreamType;
import java.io.Serializable;
/**
* @author keria
* @date 2023/10/18
* @note 直播连麦推拉流用户信息
*/
public class InteractiveUserData implements Serializable {
private static final String TAG = InteractiveUserData.class.getSimpleName();
public String channelId;
public String userId;
public AlivcLivePlayVideoStreamType videoStreamType;
public AlivcLivePlayAudioStreamType audioStreamType;
public String url;
public String getKey() {
if (!TextUtils.isEmpty(channelId) && !TextUtils.isEmpty(userId)) {
return String.format("%s_%s_%s", channelId, userId, videoStreamType == AlivcLivePlayVideoStreamType.STREAM_SCREEN ? "screen" : "camera");
} else if (!TextUtils.isEmpty(url)) {
return url;
}
Log.e(TAG, "invalid interactive user data");
return null;
}
@Override
public String toString() {
return getKey();
}
}

View File

@@ -0,0 +1,5 @@
package com.alivc.live.interactive_common.listener;
public interface ConnectionLostListener {
void onConfirm();
}

View File

@@ -0,0 +1,17 @@
package com.alivc.live.interactive_common.listener;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.player.annotations.AlivcLivePlayError;
public abstract class InteractLivePushPullListener {
public void onPullSuccess(InteractiveUserData userData) {}
public void onPullError(InteractiveUserData userData, AlivcLivePlayError errorType, String errorMsg) {}
public void onPullStop(InteractiveUserData userData) {}
public void onPushSuccess(){}
public void onPushError(){}
public void onVideoEnabled(boolean enable){}
public void onConnectionLost(){}
public void onReceiveSEIMessage(int payload, byte[] data) {}
public void onPlayerSei(int payload, byte[] uuid, byte[] data) {}
public void onReceiveSEIDelay(String src, String type, String msg) {}
}

View File

@@ -0,0 +1,10 @@
package com.alivc.live.interactive_common.listener;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
public abstract class InteractLiveTipsViewListener {
public void onCancel() {}
public void onConfirm() {}
public void onInputConfirm(InteractiveUserData userData) {}
public void onQrClick() {}
}

View File

@@ -0,0 +1,67 @@
package com.alivc.live.interactive_common.manager;
import android.graphics.Bitmap;
import android.os.Handler;
import com.alivc.live.commonutils.BitmapUtil;
import com.alivc.live.commonutils.ContextUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author keria
* @date 2023/9/15
* @brief 时间戳文字水印管理类
* @note 每秒生成一帧文字水印的bitmap回调给外层进行添加水印处理
*/
public class TimestampWatermarkManager {
private static final long TIME_INTERVAL = 1000L;
private final Handler sHandler = new Handler();
private final Runnable sRunnable = new Runnable() {
@Override
public void run() {
processEvent();
sHandler.postDelayed(this, TIME_INTERVAL);
}
};
private OnWatermarkListener mOnWatermarkListener;
public void init(OnWatermarkListener listener) {
mOnWatermarkListener = listener;
startHandler();
}
public void destroy() {
removeHandler();
mOnWatermarkListener = null;
}
public void processEvent() {
if (mOnWatermarkListener != null) {
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
Bitmap bitmap = BitmapUtil.createTextBitmap(ContextUtils.getContext(), timestamp);
mOnWatermarkListener.onWatermarkUpdate(bitmap);
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
}
}
private void startHandler() {
sHandler.postDelayed(sRunnable, TIME_INTERVAL);
}
private void removeHandler() {
sHandler.removeCallbacksAndMessages(null);
}
public interface OnWatermarkListener {
public void onWatermarkUpdate(Bitmap bitmap);
}
}

View File

@@ -0,0 +1,17 @@
package com.alivc.live.interactive_common.utils;
public enum InteractLiveIntent {
/**
* 关闭页面
*/
INTENT_FINISH,
/**
* 停止拉流
*/
INTENT_STOP_PULL,
/**
*
* 停止推流
*/
INTENT_STOP_PUSH
}

View File

@@ -0,0 +1,56 @@
package com.alivc.live.interactive_common.utils;
import com.alivc.live.annotations.AlivcLiveCameraCaptureOutputPreference;
import com.alivc.live.pusher.AlivcLivePushConfig;
/**
* 直播连麦公共配置
*
* @note 用于传递配置页的一些公共参数到直播连麦页面
*/
public class LivePushGlobalConfig {
/**
* 推流配置
*/
public static AlivcLivePushConfig mAlivcLivePushConfig = new AlivcLivePushConfig();
/**
* 多人互动模式/PK
*/
public static boolean IS_MULTI_INTERACT = false;
/**
* 开启 Data Channel 自定义消息通道
*/
public static boolean IS_DATA_CHANNEL_MESSAGE_ENABLE = false;
/**
* 开启强制耳返
*/
public static boolean IS_EARBACK_OPEN_WITHOUT_HEADSET = false;
/**
* H5兼容模式可与web连麦互通
*/
public static boolean IS_H5_COMPATIBLE = false;
/**
* 美颜
*/
public static boolean ENABLE_BEAUTY = true;
/**
* 本地日志
*/
public static boolean ENABLE_LOCAL_LOG = true;
/**
* 水印
*/
public static boolean ENABLE_WATER_MARK = false;
/**
* 摄像头采集偏好
*/
public static AlivcLiveCameraCaptureOutputPreference CAMERA_CAPTURE_OUTPUT_PREFERENCE = AlivcLiveCameraCaptureOutputPreference.PREVIEW;
}

View File

@@ -0,0 +1,66 @@
package com.alivc.live.interactive_common.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.alivc.live.commonui.avdialog.AUILiveDialog;
import com.alivc.live.interactive_common.R;
import com.alivc.live.interactive_common.listener.ConnectionLostListener;
public class ConnectionLostTipsView extends ConstraintLayout {
private final Context mContext;
private final View inflate;
private TextView mConfirmTextView;
private AUILiveDialog mAUILiveDialog;
private ConnectionLostListener mConnectionLostListener;
public ConnectionLostTipsView(@NonNull Context context) {
this(context,null);
}
public ConnectionLostTipsView(@NonNull Context context, AttributeSet attrs) {
this(context, attrs,-1);
}
public ConnectionLostTipsView(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
mAUILiveDialog = new AUILiveDialog(context);
inflate = LayoutInflater.from(mContext).inflate(R.layout.layout_connect_lost_tips_view, this, true);
initView();
initListener();
mAUILiveDialog.setContentView(this);
}
private void initView(){
mConfirmTextView = inflate.findViewById(R.id.tv_confirm);
}
private void initListener(){
mConfirmTextView.setOnClickListener(view -> {
if(mConnectionLostListener != null){
mConnectionLostListener.onConfirm();
}
});
}
public void show(){
mAUILiveDialog.show();
}
public void hide(){
mAUILiveDialog.dismiss();
}
public void setConnectionLostListener(ConnectionLostListener listener){
this.mConnectionLostListener = listener;
}
}

View File

@@ -0,0 +1,57 @@
package com.alivc.live.interactive_common.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.alivc.live.interactive_common.R;
public class InteractLiveRadioButton extends ConstraintLayout {
private final Context mContext;
private ConstraintLayout mConstraintLayoutRootView;
private TextView mTitleTextView;
public InteractLiveRadioButton(Context context) {
this(context, null);
}
public InteractLiveRadioButton(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public InteractLiveRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
initView();
initAttribute(attrs);
}
private void initView() {
View mRootView = LayoutInflater.from(mContext).inflate(R.layout.layout_interact_live_radio_button, this, true);
mConstraintLayoutRootView = mRootView.findViewById(R.id.root);
mTitleTextView = mRootView.findViewById(R.id.tv_title);
}
private void initAttribute(AttributeSet attrs) {
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.InteractLiveRadioButton);
String mText = typedArray.getString(R.styleable.InteractLiveRadioButton_ilrb_text);
typedArray.recycle();
mTitleTextView.setText(mText);
}
public void setChecked(boolean isChecked) {
if (isChecked) {
mConstraintLayoutRootView.setBackground(getResources().getDrawable(R.drawable.shape_interact_live_selected));
} else {
mConstraintLayoutRootView.setBackground(getResources().getDrawable(R.drawable.shape_interact_live_unselected));
}
}
}

View File

@@ -0,0 +1,217 @@
package com.alivc.live.interactive_common.widget;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.alivc.live.interactive_common.R;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.interactive_common.listener.InteractLiveTipsViewListener;
import com.alivc.live.player.annotations.AlivcLivePlayAudioStreamType;
import com.alivc.live.player.annotations.AlivcLivePlayVideoStreamType;
/**
* 直播连麦用户信息输入框
*/
public class InteractiveCommonInputView extends ConstraintLayout {
public enum ViewType {
INTERACTIVE,
PK,
BARE_STREAM
}
private ViewType mViewType;
private final Context mContext;
private final View inflate;
private TextView mCancelTextView;
private TextView mConfirmTextView;
private TextView mTipsTextView;
private EditText mInputUserIdEditText;
private EditText mInputRoomIdEditText;
private EditText mInputUrlEditText;
private ImageView mClearUserIdImageView;
private ImageView mClearRoomIdImageView;
private ImageView mScanQrImageView;
private LinearLayout mVideoRadioGroupLayout;
private RadioGroup mVideoStreamTypeRadioGroup;
private RadioButton mVideoCamaraRadioButton;
private RadioButton mVideoScreenRadioButton;
private LinearLayout mAudioRadioGroupLayout;
private RadioGroup mAudioStreamTypeRadioGroup;
private RadioButton mAudioMicRadioButton;
private RadioButton mAudioDualRadioButton;
private InteractLiveTipsViewListener mInteractLiveTipsViewListener;
private boolean mShowInputView;
public InteractiveCommonInputView(@NonNull Context context) {
this(context, null);
}
public InteractiveCommonInputView(@NonNull Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public InteractiveCommonInputView(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
inflate = LayoutInflater.from(mContext).inflate(R.layout.layout_interactive_common_input, this, true);
initView();
initListener();
}
private void initView() {
mCancelTextView = inflate.findViewById(R.id.tv_cancel);
mConfirmTextView = inflate.findViewById(R.id.tv_confirm);
mTipsTextView = inflate.findViewById(R.id.tv_tips);
mInputUserIdEditText = inflate.findViewById(R.id.et_input_user_id);
mInputRoomIdEditText = inflate.findViewById(R.id.et_input_room_id);
mInputUrlEditText = inflate.findViewById(R.id.et_input_url);
mClearUserIdImageView = inflate.findViewById(R.id.iv_clear_user_id);
mClearRoomIdImageView = inflate.findViewById(R.id.iv_clear_room_id);
mScanQrImageView = inflate.findViewById(R.id.iv_scan_qr);
mVideoRadioGroupLayout = inflate.findViewById(R.id.video_radio_group);
mVideoStreamTypeRadioGroup = inflate.findViewById(R.id.rg_video_stream_type);
mVideoCamaraRadioButton = inflate.findViewById(R.id.rb_camera);
mVideoScreenRadioButton = inflate.findViewById(R.id.rb_screen);
mAudioRadioGroupLayout = inflate.findViewById(R.id.audio_radio_group);
mAudioStreamTypeRadioGroup = inflate.findViewById(R.id.rg_audio_stream_type);
mAudioMicRadioButton = inflate.findViewById(R.id.rb_mic);
mAudioDualRadioButton = inflate.findViewById(R.id.rb_dual);
}
private void initListener() {
mCancelTextView.setOnClickListener(view -> {
if (mInteractLiveTipsViewListener != null) {
mInteractLiveTipsViewListener.onCancel();
}
});
mConfirmTextView.setOnClickListener(view -> {
if (mInteractLiveTipsViewListener == null) {
return;
}
if (!mShowInputView) {
mInteractLiveTipsViewListener.onConfirm();
return;
}
InteractiveUserData inputData = new InteractiveUserData();
if (mViewType == ViewType.INTERACTIVE) {
inputData.userId = mInputUserIdEditText.getText().toString().trim();
} else if (mViewType == ViewType.PK) {
inputData.userId = mInputUserIdEditText.getText().toString().trim();
inputData.channelId = mInputRoomIdEditText.getText().toString().trim();
} else if (mViewType == ViewType.BARE_STREAM) {
inputData.url = mInputUrlEditText.getText().toString().trim();
}
inputData.videoStreamType = (mVideoStreamTypeRadioGroup.getCheckedRadioButtonId() == mVideoCamaraRadioButton.getId())
? AlivcLivePlayVideoStreamType.STREAM_CAMERA
: AlivcLivePlayVideoStreamType.STREAM_SCREEN;
inputData.audioStreamType = (mAudioStreamTypeRadioGroup.getCheckedRadioButtonId() == mAudioMicRadioButton.getId())
? AlivcLivePlayAudioStreamType.STREAM_MIC
: AlivcLivePlayAudioStreamType.STREAM_DUAL;
mInteractLiveTipsViewListener.onInputConfirm(inputData);
});
mClearUserIdImageView.setOnClickListener(view -> mInputUserIdEditText.setText(""));
mClearRoomIdImageView.setOnClickListener(view -> mInputRoomIdEditText.setText(""));
mScanQrImageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mInteractLiveTipsViewListener != null) {
mInteractLiveTipsViewListener.onQrClick();
}
}
});
}
public void setViewType(ViewType viewType) {
mViewType = viewType;
if (viewType == ViewType.INTERACTIVE) {
mInputUserIdEditText.setVisibility(View.VISIBLE);
mClearUserIdImageView.setVisibility(View.VISIBLE);
mInputRoomIdEditText.setVisibility(View.GONE);
mClearRoomIdImageView.setVisibility(View.GONE);
mInputUrlEditText.setVisibility(View.GONE);
mScanQrImageView.setVisibility(View.GONE);
mTipsTextView.setVisibility(View.VISIBLE);
mAudioRadioGroupLayout.setVisibility(View.VISIBLE);
mVideoRadioGroupLayout.setVisibility(View.VISIBLE);
} else if (viewType == ViewType.PK) {
mInputUserIdEditText.setVisibility(View.VISIBLE);
mClearUserIdImageView.setVisibility(View.VISIBLE);
mInputRoomIdEditText.setVisibility(View.VISIBLE);
mClearRoomIdImageView.setVisibility(View.VISIBLE);
mInputUrlEditText.setVisibility(View.GONE);
mScanQrImageView.setVisibility(View.GONE);
mTipsTextView.setVisibility(View.VISIBLE);
mAudioRadioGroupLayout.setVisibility(View.VISIBLE);
mVideoRadioGroupLayout.setVisibility(View.VISIBLE);
} else if (viewType == ViewType.BARE_STREAM) {
mInputUserIdEditText.setVisibility(View.GONE);
mInputRoomIdEditText.setVisibility(View.GONE);
mClearUserIdImageView.setVisibility(View.GONE);
mClearRoomIdImageView.setVisibility(View.GONE);
mInputUrlEditText.setVisibility(View.VISIBLE);
mScanQrImageView.setVisibility(View.VISIBLE);
mTipsTextView.setVisibility(View.VISIBLE);
mAudioRadioGroupLayout.setVisibility(View.GONE);
mVideoRadioGroupLayout.setVisibility(View.GONE);
}
}
public void setDefaultRoomId(String roomId) {
if (!TextUtils.isEmpty(roomId)) {
mInputRoomIdEditText.setText(roomId);
}
}
public void showInputView(String content, boolean showInputView) {
this.mShowInputView = showInputView;
if (!mShowInputView) {
mInputUserIdEditText.setVisibility(View.GONE);
mInputRoomIdEditText.setVisibility(View.GONE);
mInputUrlEditText.setVisibility(View.GONE);
mClearUserIdImageView.setVisibility(View.GONE);
mClearRoomIdImageView.setVisibility(View.GONE);
mTipsTextView.setVisibility(View.VISIBLE);
mTipsTextView.setText(content);
mAudioRadioGroupLayout.setVisibility(View.GONE);
mVideoRadioGroupLayout.setVisibility(View.GONE);
} else {
setViewType(mViewType);
}
}
public void setQrResult(String content) {
mInputUrlEditText.setText(content);
}
public void setOnInteractLiveTipsViewListener(InteractLiveTipsViewListener listener) {
this.mInteractLiveTipsViewListener = listener;
}
}

View File

@@ -0,0 +1,108 @@
package com.alivc.live.interactive_common.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.alivc.live.interactive_common.R;
/**
* 连麦 View
*/
public class InteractiveConnectView extends LinearLayout {
private FrameLayout mConnectFrameLayout;
private TextView mConnectTextView;
private OnConnectClickListener mOnConnectClickListener;
public InteractiveConnectView(@NonNull Context context) {
super(context);
init(context);
}
public InteractiveConnectView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public InteractiveConnectView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
setOrientation(LinearLayout.VERTICAL);
View inflate = LayoutInflater.from(context).inflate(R.layout.layout_interactive_connect_view, this, true);
mConnectFrameLayout = inflate.findViewById(R.id.fl_un_connect);
mConnectTextView = inflate.findViewById(R.id.tv_connect);
initListener();
}
private void initListener() {
mConnectTextView.setOnClickListener(view -> {
if (mOnConnectClickListener != null) {
mOnConnectClickListener.onConnectClick();
}
});
}
public void isShow(boolean isShowSurfaceView) {
mConnectFrameLayout.setVisibility(isShowSurfaceView ? View.INVISIBLE : View.VISIBLE);
}
public void setDefaultText(String text) {
mConnectTextView.setText(text);
}
/**
* 开始连接
*/
public void connected() {
connected(getResources().getString(R.string.interact_stop_connect));
}
/**
* 断开连接
*/
public void unConnected() {
unConnected(getResources().getString(R.string.interact_start_connect));
}
public void connected(String text) {
mConnectTextView.setText(text);
mConnectTextView.setBackground(getResources().getDrawable(R.drawable.shape_interact_live_un_connect_btn_bg));
}
public void unConnected(String text) {
mConnectTextView.setText(text);
mConnectTextView.setBackground(getResources().getDrawable(R.drawable.shape_pysh_btn_bg));
}
public FrameLayout getConnectFrameLayout() {
return mConnectFrameLayout;
}
public TextView getConnectTextView() {
return mConnectTextView;
}
public void setConnectClickListener(OnConnectClickListener listener) {
this.mOnConnectClickListener = listener;
}
/**
* 连麦按钮点击监听
*/
public interface OnConnectClickListener {
void onConnectClick();
}
}

View File

@@ -0,0 +1,116 @@
package com.alivc.live.interactive_common.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.alivc.live.interactive_common.R;
/**
* @author keria
* @date 2024/5/28
* @brief 直播连麦窗格控制操作栏
*/
public class InteractivePaneControlView extends LinearLayout {
private ImageView mInteractMuteImageView;
private ImageView mInteractSetEmptyView;
private ImageView mInteractResizeView;
private OnClickEventListener mOnClickEventListener;
private boolean mMuteAudio = false;
private boolean mEmptyView = false;
private boolean mResize = false;
public InteractivePaneControlView(Context context) {
super(context);
init(context);
}
public InteractivePaneControlView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public InteractivePaneControlView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.layout_interactive_pane_control, this, true);
mInteractMuteImageView = findViewById(R.id.iv_mute_audio);
mInteractSetEmptyView = findViewById(R.id.iv_show_view);
mInteractResizeView = findViewById(R.id.iv_view_resize);
setupListeners();
}
private void setupListeners() {
mInteractMuteImageView.setOnClickListener(v -> toggleState(
() -> mMuteAudio = !mMuteAudio,
() -> mInteractMuteImageView.setImageResource(mMuteAudio ? R.drawable.ic_interact_mute : R.drawable.ic_interact_unmute),
mOnClickEventListener != null ? () -> mOnClickEventListener.onClickMuteAudio(mMuteAudio) : null
));
mInteractSetEmptyView.setOnClickListener(v -> toggleState(
() -> mEmptyView = !mEmptyView,
() -> mInteractSetEmptyView.setImageResource(mEmptyView ? R.drawable.ic_view_container_hide : R.drawable.ic_view_container_show),
mOnClickEventListener != null ? () -> mOnClickEventListener.onClickEmptyView(mEmptyView) : null
));
mInteractResizeView.setOnClickListener(v -> toggleState(
() -> mResize = !mResize,
() -> mInteractResizeView.setImageResource(mResize ? R.drawable.ic_full_screen : R.drawable.ic_half_screen),
mOnClickEventListener != null ? () -> mOnClickEventListener.onClickResize(mResize) : null
));
}
private void toggleState(@Nullable Runnable viewState, @Nullable Runnable viewStateToggler, @Nullable Runnable eventCallback) {
// 执行状态转换
if (viewState != null) {
viewState.run();
}
// 更新视图状态
if (viewStateToggler != null) {
viewStateToggler.run();
}
// 执行事件回调
if (eventCallback != null) {
eventCallback.run();
}
}
public void enableMute(boolean mute) {
if (mInteractMuteImageView != null) {
mInteractMuteImageView.setVisibility(mute ? View.VISIBLE : View.GONE);
}
}
public void setOnClickEventListener(OnClickEventListener listener) {
this.mOnClickEventListener = listener;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mOnClickEventListener = null;
}
public interface OnClickEventListener {
void onClickMuteAudio(boolean mute);
void onClickEmptyView(boolean empty);
void onClickResize(boolean resize);
}
}

View File

@@ -0,0 +1,83 @@
package com.alivc.live.interactive_common.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.alivc.live.interactive_common.R;
public class InteractiveQrEditView extends ConstraintLayout {
private TextView titleView;
private EditText etContentView;
private ImageView qrImageView;
private OnQrClickListener mQrClickListener;
public InteractiveQrEditView(@NonNull Context context) {
super(context);
init(context, null);
}
public InteractiveQrEditView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public InteractiveQrEditView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
View inflateView = LayoutInflater.from(context).inflate(R.layout.layout_interactive_qr_input_view, this, true);
titleView = inflateView.findViewById(R.id.tv_interactive_pull_url);
etContentView = inflateView.findViewById(R.id.et_interactive_pull_url);
qrImageView = inflateView.findViewById(R.id.iv_qr_pull_url);
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.InteractiveQrEditView);
String title = typedArray.getString(R.styleable.InteractiveQrEditView_title);
typedArray.recycle();
titleView.setText(title);
}
initListener();
}
private void initListener() {
qrImageView.setOnClickListener(view -> {
if (mQrClickListener != null) {
mQrClickListener.onQrClick();
}
});
}
public void setTitle(String title) {
titleView.setText(title);
}
public void setEditText(String text) {
etContentView.setText(text);
}
public String getEditText() {
return etContentView.getText().toString();
}
public void setOnQrClickListener(OnQrClickListener listener) {
this.mQrClickListener = listener;
}
public interface OnQrClickListener {
void onQrClick();
}
}

View File

@@ -0,0 +1,134 @@
package com.alivc.live.interactive_common.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.alivc.live.interactive_common.R;
/**
* @Author keria
* @Date 2024/5/28
* @Brief 直播连麦房间控制操作栏
*/
public class InteractiveRoomControlView extends LinearLayout {
private ImageView mSwitchCameraIv;
private ImageView mUseSpeakerPhoneIv;
private ImageView mMuteAudioIv;
private ImageView mMuteVideoIv;
private ImageView mEnableAudioCaptureIv;
private ImageView mEnableVideoCaptureIv;
private boolean mUseSpeakerPhone = false;
private boolean mMuteAudio = false;
private boolean mMuteVideo = false;
private boolean mEnableAudioCapture = true;
private boolean mEnableVideoCapture = true;
private OnClickEventListener mOnClickEventListener;
public InteractiveRoomControlView(Context context) {
super(context);
init(context);
}
public InteractiveRoomControlView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public InteractiveRoomControlView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.layout_interactive_room_control, this, true);
mSwitchCameraIv = findViewById(R.id.iv_camera);
mUseSpeakerPhoneIv = findViewById(R.id.iv_speaker_phone);
mMuteAudioIv = findViewById(R.id.iv_mute_audio);
mMuteVideoIv = findViewById(R.id.iv_mute_video);
mEnableAudioCaptureIv = findViewById(R.id.iv_enable_audio);
mEnableVideoCaptureIv = findViewById(R.id.iv_enable_video);
setupListeners();
}
private void setupListeners() {
mSwitchCameraIv.setOnClickListener(v -> toggleState(
null,
null,
mOnClickEventListener != null ? () -> mOnClickEventListener.onClickSwitchCamera() : null
));
mUseSpeakerPhoneIv.setOnClickListener(v -> toggleState(
() -> mUseSpeakerPhone = !mUseSpeakerPhone,
() -> mUseSpeakerPhoneIv.setImageResource(mUseSpeakerPhone ? R.drawable.ic_speaker_phone_on : R.drawable.ic_speaker_phone_off),
mOnClickEventListener != null ? () -> mOnClickEventListener.onClickSpeakerPhone(mUseSpeakerPhone) : null
));
mMuteAudioIv.setOnClickListener(v -> toggleState(
() -> mMuteAudio = !mMuteAudio,
() -> mMuteAudioIv.setImageResource(mMuteAudio ? R.drawable.ic_audio_capture_off : R.drawable.ic_audio_capture_on),
mOnClickEventListener != null ? () -> mOnClickEventListener.onClickMuteAudio(mMuteAudio) : null
));
mMuteVideoIv.setOnClickListener(v -> toggleState(
() -> mMuteVideo = !mMuteVideo,
() -> mMuteVideoIv.setImageResource(mMuteVideo ? R.drawable.ic_local_video_off : R.drawable.ic_local_video_on),
mOnClickEventListener != null ? () -> mOnClickEventListener.onClickMuteVideo(mMuteVideo) : null
));
mEnableAudioCaptureIv.setOnClickListener(v -> toggleState(
() -> mEnableAudioCapture = !mEnableAudioCapture,
() -> mEnableAudioCaptureIv.setImageResource(mEnableAudioCapture ? R.drawable.ic_audio_capture_on : R.drawable.ic_audio_capture_off),
mOnClickEventListener != null ? () -> mOnClickEventListener.onClickEnableAudio(mEnableAudioCapture) : null
));
mEnableVideoCaptureIv.setOnClickListener(v -> toggleState(
() -> mEnableVideoCapture = !mEnableVideoCapture,
() -> mEnableVideoCaptureIv.setImageResource(mEnableVideoCapture ? R.drawable.ic_local_video_on : R.drawable.ic_local_video_off),
mOnClickEventListener != null ? () -> mOnClickEventListener.onClickEnableVideo(mEnableVideoCapture) : null
));
}
private void toggleState(@Nullable Runnable viewState, @Nullable Runnable viewStateToggler, @Nullable Runnable eventCallback) {
// 执行状态转换
if (viewState != null) {
viewState.run();
}
// 更新视图状态
if (viewStateToggler != null) {
viewStateToggler.run();
}
// 执行事件回调
if (eventCallback != null) {
eventCallback.run();
}
}
public void setOnClickEventListener(OnClickEventListener listener) {
this.mOnClickEventListener = listener;
}
public interface OnClickEventListener {
void onClickSwitchCamera();
void onClickSpeakerPhone(boolean enable);
void onClickMuteAudio(boolean mute);
void onClickMuteVideo(boolean mute);
void onClickEnableAudio(boolean enable);
void onClickEnableVideo(boolean enable);
}
}

View File

@@ -0,0 +1,41 @@
package com.alivc.live.interactive_common.widget;
import android.widget.FrameLayout;
import android.widget.TextView;
public class MultiAlivcLiveView {
private FrameLayout mUnConnectFrameLayout;
private FrameLayout mSmallFrameLayout;
private TextView mConnectTextView;
public MultiAlivcLiveView(FrameLayout unConnectFrameLayout,FrameLayout smallFrameLayout,TextView connectTextView){
this.mUnConnectFrameLayout = unConnectFrameLayout;
this.mSmallFrameLayout = smallFrameLayout;
this.mConnectTextView = connectTextView;
}
public FrameLayout getUnConnectFrameLayout() {
return mUnConnectFrameLayout;
}
public void setUnConnectFrameLayout(FrameLayout mUnConnectFrameLayout) {
this.mUnConnectFrameLayout = mUnConnectFrameLayout;
}
public FrameLayout getSmallFrameLayout() {
return mSmallFrameLayout;
}
public void setSmallFrameLayout(FrameLayout mSmallFrameLayout) {
this.mSmallFrameLayout = mSmallFrameLayout;
}
public TextView getConnectTextView() {
return mConnectTextView;
}
public void setConnectTextView(TextView mConnectTextView) {
this.mConnectTextView = mConnectTextView;
}
}

View File

@@ -0,0 +1,61 @@
package com.alivc.live.interactive_common.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.alivc.live.commonbiz.test.AliLiveStreamURLUtil;
import com.alivc.live.interactive_common.R;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import java.util.HashMap;
/**
* 用户连麦窗口显示'房间ID'和'用户ID'
*/
public class RoomAndUserInfoView extends LinearLayout {
private TextView mInfoTv;
public RoomAndUserInfoView(@NonNull Context context) {
super(context);
init(context);
}
public RoomAndUserInfoView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public RoomAndUserInfoView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
View inflateView = LayoutInflater.from(context).inflate(R.layout.layout_room_user_info_view, this, true);
mInfoTv = inflateView.findViewById(R.id.tv_info);
}
public void setUserInfo(String channelId, String userId) {
mInfoTv.setText(String.format("Room:%s\nUser:%s", channelId, userId));
}
public void setUrl(String url) {
HashMap<String, String> params = AliLiveStreamURLUtil.parseUrl(url);
mInfoTv.setText(String.format("Room:%s\nUser:%s",
AliLiveStreamURLUtil.parseURLStreamName(params),
params.get(AliLiveStreamURLUtil.USER_ID)));
}
public void setUserData(InteractiveUserData userData) {
mInfoTv.setText(String.format("Room:%s\nUser:%s",
userData != null ? userData.channelId : "",
userData != null ? userData.userId : ""));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

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

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke android:width="1dp" android:color="#747A8C"/>
<corners android:radius="2dp" />
</shape>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#23262F" />
<corners android:radius="12dp" />
<stroke android:color="@color/colourful_border_weak" android:width="2dp" />
</shape>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#4D4DCFE1"/>
<stroke android:color="#4DCFE1" android:width="1px"/>
<corners android:radius="24dp" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 按钮的选择器,可以设置按钮在不同状态下的时候,按钮不同的颜色 -->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_checked="true" android:drawable="@drawable/thumb_on" />
<item android:drawable="@drawable/thumb_off" />
</selector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<size android:height="26dp" android:width="26dp"/>
<corners android:radius="13dp"/>
<solid android:color="#747A8C"/>
<stroke android:width="2dp"
android:color="#23262F"/>
</shape>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 高度40 -->
<size
android:width="26dp"
android:height="26dp" />
<!-- 圆角弧度 20 -->
<corners android:radius="13dp" />
<solid android:color="#FCFCFD" />
<stroke android:width="2dp"
android:color="#4DCFE1"/>
</shape>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 底层下滑条的样式选择器可控制Switch在不同状态下底下下滑条的颜色 -->
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_checked="true" android:drawable="@drawable/track_on" />
<item android:drawable="@drawable/track_off" />
</selector>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<!-- 高度30 此处设置宽度无效-->
<size android:height="26dp"/>
<!-- 圆角弧度 15 -->
<corners android:radius="13dp"/>
<!-- 变化率 定义从左到右的颜色不变 -->
<solid android:color="#23262F"/>
</shape>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<size android:height="26dp"
android:width="50dp"/>
<corners android:radius="13dp" />
<solid android:height="26dp" android:color="#4DCFE1" />
</shape>

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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1D22">
<ImageView
android:id="@+id/iv_back"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginStart="20dp"
android:src="@drawable/ic_live_action_bar_back"
app:layout_constraintBottom_toBottomOf="@id/tv_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_title" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginTop="44dp"
android:gravity="center"
android:text="@string/app_info"
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_app_id_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_marginStart="20dp"
android:text="@string/app_info_app_id"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<EditText
android:id="@+id/et_app_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginEnd="20dp"
android:digits="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_"
android:hint="@string/interact_live_user_id_hint"
android:maxLines="1"
android:singleLine="true"
android:textColor="#E6E7EC"
android:textColorHint="#E6E7EC"
android:theme="@style/AUIEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/tv_app_id_title"
app:layout_constraintTop_toBottomOf="@id/tv_app_id_title" />
<ImageView
android:id="@+id/iv_app_id_clear"
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/ic_scan_icon"
app:layout_constraintBottom_toBottomOf="@id/et_app_id"
app:layout_constraintEnd_toEndOf="@id/et_app_id"
app:layout_constraintTop_toTopOf="@id/et_app_id" />
<TextView
android:id="@+id/tv_app_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="@string/app_info_app_key"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="@id/tv_app_id_title"
app:layout_constraintTop_toBottomOf="@id/et_app_id" />
<EditText
android:id="@+id/et_app_key"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:digits="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_"
android:hint="@string/interact_live_user_id_hint"
android:maxLines="1"
android:singleLine="true"
android:textColor="#E6E7EC"
android:textColorHint="#E6E7EC"
android:theme="@style/AUIEditText"
app:layout_constraintEnd_toEndOf="@id/et_app_id"
app:layout_constraintStart_toStartOf="@id/tv_app_key"
app:layout_constraintTop_toBottomOf="@id/tv_app_key" />
<ImageView
android:id="@+id/iv_app_key_clear"
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/ic_scan_icon"
app:layout_constraintBottom_toBottomOf="@id/et_app_key"
app:layout_constraintEnd_toEndOf="@id/et_app_key"
app:layout_constraintTop_toTopOf="@id/et_app_key" />
<TextView
android:id="@+id/tv_play_domain_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="@string/app_info_play_domain"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="@id/tv_app_id_title"
app:layout_constraintTop_toBottomOf="@id/et_app_key" />
<EditText
android:id="@+id/et_play_domain"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:digits="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_"
android:hint="@string/interact_live_user_id_hint"
android:maxLines="1"
android:singleLine="true"
android:textColor="#E6E7EC"
android:textColorHint="#E6E7EC"
android:theme="@style/AUIEditText"
app:layout_constraintEnd_toEndOf="@id/et_app_id"
app:layout_constraintStart_toStartOf="@id/tv_app_id_title"
app:layout_constraintTop_toBottomOf="@id/tv_play_domain_title" />
<ImageView
android:id="@+id/iv_play_domain_clear"
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/ic_scan_icon"
app:layout_constraintBottom_toBottomOf="@id/et_play_domain"
app:layout_constraintEnd_toEndOf="@id/et_play_domain"
app:layout_constraintTop_toTopOf="@id/et_play_domain" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="44dp"
android:background="@drawable/shape_rect_blue"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="2"
android:clickable="false"
android:text="@string/interact_live_next"
android:textColor="@android:color/white"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,235 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1D22">
<ImageView
android:id="@+id/iv_back"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginStart="20dp"
android:src="@drawable/ic_live_action_bar_back"
app:layout_constraintBottom_toBottomOf="@id/tv_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_title" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginTop="44dp"
android:gravity="center"
android:text="@string/interact_live"
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_setting"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginTop="44dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:text="@string/interact_setting"
android:textColor="#FCFCFD"
android:textSize="12sp"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="40dp"
app:layout_constraintBottom_toTopOf="@id/tv_confirm"
app:layout_constraintTop_toBottomOf="@id/tv_title">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_user_id_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="@string/interact_live_user_id"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_user_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginEnd="20dp"
android:digits="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
android:hint="@string/interact_live_user_id_hint"
android:maxLength="20"
android:maxLines="1"
android:singleLine="true"
android:textColor="#E6E7EC"
android:textColorHint="#E6E7EC"
android:theme="@style/AUIEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/tv_user_id_title"
app:layout_constraintTop_toBottomOf="@id/tv_user_id_title" />
<ImageView
android:id="@+id/iv_user_id_clear"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="8dp"
android:src="@drawable/ic_close"
app:layout_constraintBottom_toBottomOf="@id/et_user_id"
app:layout_constraintEnd_toEndOf="@id/et_user_id"
app:layout_constraintTop_toTopOf="@id/et_user_id" />
<TextView
android:id="@+id/tv_room_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="@string/interact_live_room_id"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="@id/tv_user_id_title"
app:layout_constraintTop_toBottomOf="@id/et_user_id" />
<EditText
android:id="@+id/et_room_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:digits="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
android:hint="@string/interact_live_user_id_hint"
android:maxLength="20"
android:maxLines="1"
android:singleLine="true"
android:textColor="#E6E7EC"
android:textColorHint="#E6E7EC"
android:theme="@style/AUIEditText"
app:layout_constraintEnd_toEndOf="@id/et_user_id"
app:layout_constraintStart_toStartOf="@id/tv_user_id_title"
app:layout_constraintTop_toBottomOf="@id/tv_room_number" />
<ImageView
android:id="@+id/iv_room_id_clear"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="8dp"
android:src="@drawable/ic_close"
app:layout_constraintBottom_toBottomOf="@id/et_room_id"
app:layout_constraintEnd_toEndOf="@id/et_room_id"
app:layout_constraintTop_toTopOf="@id/et_room_id" />
<fragment
android:id="@+id/fragment_app_info"
android:name="com.alivc.live.interactive_common.InteractLiveAppInfoFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/et_room_id" />
<LinearLayout
android:id="@+id/role_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="20dp"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/fragment_app_info"
tools:visibility="visible">
<com.alivc.live.interactive_common.widget.InteractLiveRadioButton
android:id="@+id/radio_button_anchor"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:ilrb_text="@string/interact_live_anchor" />
<View
android:layout_width="15dp"
android:layout_height="1dp" />
<com.alivc.live.interactive_common.widget.InteractLiveRadioButton
android:id="@+id/radio_button_audience"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:ilrb_text="@string/interact_live_audience" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:orientation="vertical"
android:padding="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/role_container">
<TextView
android:id="@+id/tv_url_entrance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00000000"
android:gravity="center"
android:includeFontPadding="false"
android:lineSpacingExtra="3dp"
android:padding="10dp"
android:singleLine="true"
android:text="@string/tips_use_url_for_interactive"
android:textColor="#FCFCFD"
android:textSize="16dp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_interactive_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:includeFontPadding="false"
android:lineSpacingExtra="3dp"
android:padding="5dp"
android:textColor="#FCFCFD"
android:textSize="12dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<TextView
android:id="@+id/tv_confirm"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="44dp"
android:background="@drawable/shape_rect_blue"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="2"
android:text="@string/interact_live_confirm"
android:textColor="@android:color/white"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,299 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1D22">
<ImageView
android:id="@+id/iv_back"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginStart="20dp"
android:src="@drawable/ic_live_action_bar_back"
app:layout_constraintBottom_toBottomOf="@id/tv_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_title" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginTop="44dp"
android:gravity="center"
android:text="@string/interact_setting"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/view_divider"
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#3A3D48"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="20dp"
app:layout_constraintTop_toBottomOf="@id/view_divider"
app:layout_constraintBottom_toTopOf="@id/btn_commit">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="45dp">
<LinearLayout
android:id="@+id/tab_args_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tab_args_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="17dp"
android:layout_marginBottom="5dp"
android:text="@string/push_args"
android:textColor="#FCFCFD"
android:textSize="@dimen/font_size_28px" />
<View
android:id="@+id/tab_args_view"
android:layout_width="32dp"
android:layout_height="3dp"
android:layout_gravity="bottom|center_horizontal"
android:background="#4DCFE1" />
</LinearLayout>
<LinearLayout
android:id="@+id/tab_action_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tab_action_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="17dp"
android:layout_marginBottom="5dp"
android:text="@string/stream_pusher_tip"
android:textColor="#FCFCFD"
android:textSize="@dimen/font_size_28px" />
<View
android:id="@+id/tab_action_view"
android:layout_width="32dp"
android:layout_height="3dp"
android:layout_gravity="bottom|center_horizontal"
android:background="#4DCFE1"
android:visibility="invisible" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/multi_interactive_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/view_divider">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginTop="3dp"
android:layout_marginRight="10dp"
android:text="@string/pull_multi_interact_name_tv"
android:textColor="#FCFCFD"
android:textSize="14sp" />
<Switch
android:id="@+id/multi_interaction_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="16dp"
android:checked="false"
android:textOff=""
android:textOn=""
android:thumb="@drawable/thumb"
android:track="@drawable/track" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_alignParentBottom="true"
android:layout_marginTop="5dp"
android:background="#3A3D48" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginTop="3dp"
android:layout_marginRight="10dp"
android:text="@string/h5_compatible_name_tv"
android:textColor="#FCFCFD"
android:textSize="14sp" />
<Switch
android:id="@+id/h5_compatible_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="16dp"
android:checked="false"
android:textOff=""
android:textOn=""
android:thumb="@drawable/thumb"
android:track="@drawable/track" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_alignParentBottom="true"
android:layout_marginTop="5dp"
android:background="#3A3D48" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginTop="3dp"
android:layout_marginRight="10dp"
android:text="@string/data_channel_name_tv"
android:textColor="#FCFCFD"
android:textSize="14sp" />
<Switch
android:id="@+id/data_channel_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="16dp"
android:checked="false"
android:textOff=""
android:textOn=""
android:thumb="@drawable/thumb"
android:track="@drawable/track" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_alignParentBottom="true"
android:layout_marginTop="5dp"
android:background="#3A3D48" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginTop="3dp"
android:layout_marginRight="10dp"
android:text="@string/earback_open_without_headset"
android:textColor="#FCFCFD"
android:textSize="14sp" />
<Switch
android:id="@+id/earback_open_without_headset_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="16dp"
android:checked="false"
android:textOff=""
android:textOn=""
android:thumb="@drawable/thumb"
android:track="@drawable/track" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_alignParentBottom="true"
android:layout_marginTop="5dp"
android:background="#3A3D48" />
</RelativeLayout>
</LinearLayout>
<com.alivc.live.commonui.configview.LivePushSettingView
android:id="@+id/setting_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<Button
android:id="@+id/btn_commit"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
android:background="@drawable/shape_pysh_btn_bg"
android:gravity="center"
android:orientation="horizontal"
android:text="@string/interact_live_confirm"
android:textColor="#FCFCFD"
android:textSize="18sp"
android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1D22">
<TextView
android:id="@+id/tv_app_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="39dp"
android:text="@string/app_info"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:padding="8dp"
android:src="@drawable/ic_chevron_down"
app:layout_constraintBottom_toBottomOf="@id/tv_app_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_app_info" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_app_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/iv_arrow"
app:layout_constraintStart_toStartOf="@id/tv_app_info"
app:layout_constraintTop_toBottomOf="@id/tv_app_info"
tools:visibility="visible">
<TextView
android:id="@+id/tv_app_id_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_info_app_id"
android:textColor="#B2B7C4"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_app_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#B2B7C4"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_app_id_title" />
<TextView
android:id="@+id/tv_app_key_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/app_info_app_key"
android:textColor="#B2B7C4"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_app_id_title" />
<TextView
android:id="@+id/tv_app_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#B2B7C4"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_app_key_title" />
<TextView
android:id="@+id/tv_play_domain_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/app_info_play_domain"
android:textColor="#B2B7C4"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_app_key_title" />
<TextView
android:id="@+id/tv_play_domain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#B2B7C4"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_play_domain_title" />
<ImageView
android:id="@+id/iv_edit"
android:layout_width="16dp"
android:layout_height="36dp"
android:paddingVertical="10dp"
android:src="@drawable/ic_edit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_play_domain" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="10dp"
android:background="#3A3D48"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cl_app_info" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,43 @@
<?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="312dp"
android:layout_height="128dp"
android:background="@drawable/shape_interact_live_dialog_bg">
<TextView
android:id="@+id/tv_tips"
android:layout_width="wrap_content"
android:layout_height="70dp"
android:textColor="#FCFCFD"
android:textSize="16sp"
android:gravity="center"
android:text="@string/interact_live_lost_connect"
app:layout_constraintBottom_toBottomOf="@id/view_horizontal_line"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/view_horizontal_line"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#3A3D48"
app:layout_constraintTop_toBottomOf="@id/tv_tips"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="0dp"
android:layout_height="48dp"
android:gravity="center"
android:text="@string/interact_live_confirm"
android:textColor="#4DCFE1"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view_horizontal_line" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="@drawable/shape_interact_live_unselected">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="12dp"
android:textColor="#FCFCFD"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:src="@drawable/ic_decorate"
app:layout_constraintEnd_toEndOf="@id/tv_title"
app:layout_constraintStart_toStartOf="@id/tv_title"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginBottom="15dp"
android:src="@drawable/ic_anchor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="312dp"
android:layout_height="128dp"
android:background="@drawable/shape_interact_live_dialog_bg">
<TextView
android:id="@+id/tv_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/view_horizontal_line"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_input"
android:layout_width="0dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:background="@drawable/shape_interact_live_edit_bg"
android:gravity="start|center_vertical"
android:hint="@string/interact_live_connect_tips"
android:maxLines="1"
android:paddingStart="10dp"
android:paddingEnd="0dp"
android:textColor="#E6E7EC"
android:textColorHint="#747A8C"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/view_horizontal_line"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<ImageView
android:id="@+id/iv_clear"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_close"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/et_input"
app:layout_constraintEnd_toEndOf="@id/et_input"
app:layout_constraintTop_toTopOf="@id/et_input"
tools:visibility="visible" />
<View
android:id="@+id/view_horizontal_line"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#3A3D48"
app:layout_constraintBottom_toTopOf="@id/tv_cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/tv_cancel"
android:layout_width="0dp"
android:layout_height="48dp"
android:gravity="center"
android:text="@string/interact_live_cancel"
android:textColor="#B2B7C4"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/view_vertical_line"
app:layout_constraintStart_toStartOf="parent" />
<View
android:id="@+id/view_vertical_line"
android:layout_width="1dp"
android:layout_height="48dp"
android:background="#3A3D48"
app:layout_constraintEnd_toStartOf="@id/tv_confirm"
app:layout_constraintStart_toEndOf="@id/tv_cancel"
app:layout_constraintTop_toTopOf="@id/tv_cancel" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="0dp"
android:layout_height="48dp"
android:gravity="center"
android:text="@string/interact_live_confirm"
android:textColor="#4DCFE1"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/view_vertical_line" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,258 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="312dp"
android:layout_height="wrap_content"
android:background="@drawable/shape_interact_live_dialog_bg">
<TextView
android:id="@+id/tv_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/view_horizontal_line"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_input_user_id"
android:layout_width="0dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:background="@drawable/shape_interact_live_edit_bg"
android:digits="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
android:gravity="start|center_vertical"
android:hint="@string/interact_live_connect_author_tips"
android:maxLines="1"
android:paddingStart="10dp"
android:paddingEnd="0dp"
android:textColor="#E6E7EC"
android:textColorHint="#747A8C"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<ImageView
android:id="@+id/iv_clear_user_id"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_close"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/et_input_user_id"
app:layout_constraintEnd_toEndOf="@id/et_input_user_id"
app:layout_constraintTop_toTopOf="@id/et_input_user_id" />
<EditText
android:id="@+id/et_input_room_id"
android:layout_width="0dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:background="@drawable/shape_interact_live_edit_bg"
android:digits="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
android:gravity="start|center_vertical"
android:hint="@string/interact_live_connect_room_tips"
android:maxLines="1"
android:paddingStart="10dp"
android:paddingEnd="0dp"
android:textColor="#E6E7EC"
android:textColorHint="#747A8C"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_input_user_id"
tools:visibility="visible" />
<ImageView
android:id="@+id/iv_clear_room_id"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_close"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/et_input_room_id"
app:layout_constraintEnd_toEndOf="@id/et_input_room_id"
app:layout_constraintTop_toTopOf="@id/et_input_room_id" />
<EditText
android:id="@+id/et_input_url"
android:layout_width="0dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:background="@drawable/shape_interact_live_edit_bg"
android:digits="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
android:gravity="start|center_vertical"
android:hint="@string/interact_live_url_connect_tips"
android:maxLines="1"
android:paddingStart="10dp"
android:paddingEnd="0dp"
android:textColor="#E6E7EC"
android:textColorHint="#747A8C"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_input_room_id"
tools:visibility="visible" />
<ImageView
android:id="@+id/iv_scan_qr"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_scan_icon"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/et_input_url"
app:layout_constraintEnd_toEndOf="@id/et_input_url"
app:layout_constraintTop_toTopOf="@id/et_input_url" />
<LinearLayout
android:id="@+id/video_radio_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="10dp"
android:paddingVertical="5dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_input_url">
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="@string/interact_live_video_stream_type"
android:textColor="#FCFCFD" />
<RadioGroup
android:id="@+id/rg_video_stream_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_camera"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/interact_live_video_stream_type_camera"
android:textColor="#FCFCFD" />
<RadioButton
android:id="@+id/rb_screen"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="@string/interact_live_video_stream_type_screen"
android:textColor="#FCFCFD" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/audio_radio_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="10dp"
android:paddingVertical="5dp"
app:layout_constraintBottom_toBottomOf="@id/view_horizontal_line"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/video_radio_group">
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="@string/interact_live_audio_stream_type"
android:textColor="#FCFCFD" />
<RadioGroup
android:id="@+id/rg_audio_stream_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_mic"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/interact_live_audio_stream_type_mic"
android:textColor="#FCFCFD" />
<RadioButton
android:id="@+id/rb_dual"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:text="@string/interact_live_audio_stream_type_dual"
android:textColor="#FCFCFD" />
</RadioGroup>
</LinearLayout>
<View
android:id="@+id/view_horizontal_line"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#3A3D48"
app:layout_constraintBottom_toTopOf="@id/tv_cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/tv_cancel"
android:layout_width="0dp"
android:layout_height="48dp"
android:gravity="center"
android:text="@string/interact_live_cancel"
android:textColor="#B2B7C4"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/view_vertical_line"
app:layout_constraintStart_toStartOf="parent" />
<View
android:id="@+id/view_vertical_line"
android:layout_width="1dp"
android:layout_height="48dp"
android:background="#3A3D48"
app:layout_constraintEnd_toStartOf="@id/tv_confirm"
app:layout_constraintStart_toEndOf="@id/tv_cancel"
app:layout_constraintTop_toTopOf="@id/tv_cancel" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="0dp"
android:layout_height="48dp"
android:gravity="center"
android:text="@string/interact_live_confirm"
android:textColor="#4DCFE1"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/view_vertical_line" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<FrameLayout
android:id="@+id/fl_un_connect"
android:layout_width="95dp"
android:layout_height="160dp"
android:layout_marginBottom="15dp"
android:background="#23262F">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_interact_live_un_connect_bg" />
</FrameLayout>
<TextView
android:id="@+id/tv_connect"
android:layout_width="94dp"
android:layout_height="30dp"
android:background="@drawable/shape_pysh_btn_bg"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="1"
android:text="@string/interact_start_connect"
android:textColor="@android:color/white"
android:textSize="16sp" />
</merge>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_mute_audio"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="3dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_interact_unmute" />
<ImageView
android:id="@+id/iv_show_view"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="3dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_view_container_show" />
<ImageView
android:id="@+id/iv_view_resize"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="3dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_half_screen" />
</LinearLayout>

View File

@@ -0,0 +1,43 @@
<?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="wrap_content">
<TextView
android:id="@+id/tv_interactive_pull_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/interact_live_pull_url"
android:textColor="#FCFCFD"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_interactive_pull_url"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="22dp"
android:layout_marginEnd="20dp"
android:maxLines="1"
android:singleLine="true"
android:textColor="#E6E7EC"
android:textColorHint="#E6E7EC"
android:theme="@style/AUIEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/tv_interactive_pull_url"
app:layout_constraintTop_toBottomOf="@id/tv_interactive_pull_url" />
<ImageView
android:id="@+id/iv_qr_pull_url"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="8dp"
android:src="@drawable/ic_scan_icon"
app:layout_constraintBottom_toBottomOf="@id/et_interactive_pull_url"
app:layout_constraintEnd_toEndOf="@id/et_interactive_pull_url"
app:layout_constraintTop_toTopOf="@id/et_interactive_pull_url" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_camera"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="3dp"
android:src="@drawable/ic_camera" />
<ImageView
android:id="@+id/iv_speaker_phone"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="10dp"
android:padding="3dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_speaker_phone_off" />
<ImageView
android:id="@+id/iv_mute_audio"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="10dp"
android:padding="3dp"
android:src="@drawable/ic_audio_capture_on" />
<ImageView
android:id="@+id/iv_mute_video"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="10dp"
android:padding="3dp"
android:src="@drawable/ic_local_video_on" />
<ImageView
android:id="@+id/iv_enable_audio"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="10dp"
android:padding="3dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_audio_capture_on" />
<ImageView
android:id="@+id/iv_enable_video"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="10dp"
android:padding="3dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_local_video_on" />
</LinearLayout>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FCFCFD"
android:textSize="15dp" />
</LinearLayout>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_interact_name_tv">Connect Interact</string>
<string name="pull_interact_name_url">Connect Input URL</string>
<string name="pull_multi_interact_name_tv">Multi Connect Interact/PK</string>
<string name="h5_compatible_name_tv">H5 Compatible</string>
<string name="earback_open_without_headset">Earback Without Headset</string>
<string name="data_channel_name_tv">Data Channel</string>
<string name="external_audio_stream_tv">External Audio Stream</string>
<string name="external_video_stream_tv">External Video Stream</string>
<string name="dual_audio_stream">Push Dual Audio</string>
<string name="screen_share_stream">Push Screen Share</string>
<string name="change_resolution_and_fps">Change Resolution FPS</string>
<string name="force_ear_monitor">Force Ear Monitor</string>
<string name="push_interactive_setting_audio_only">Audio Only</string>
<string name="push_interactive_setting_gop">GOP</string>
<string name="push_interactive_setting_hardware_decode">Enable Hardware Decode</string>
<string name="push_interactive_h265">H265</string>
<string name="interact_live">Interact Live</string>
<string name="interact_live_url_title">Interact Live URL</string>
<string name="interact_live_push_url">Interact Live PUSH URL</string>
<string name="interact_live_pull_url">Interact Live PULL URL</string>
<string name="interact_start_push">Start Push</string>
<string name="interact_stop_push">Stop Push</string>
<string name="pk_live">PK Interaction</string>
<string name="interact_setting">Interact Setting</string>
<string name="interact_live_cancel">Cancel</string>
<string name="interact_live_confirm">Confirm</string>
<string name="interact_live_audio_stream_type">Audio Stream</string>
<string name="interact_live_audio_stream_type_mic">Mic</string>
<string name="interact_live_audio_stream_type_dual">Dual</string>
<string name="interact_live_video_stream_type">Video Stream</string>
<string name="interact_live_video_stream_type_camera">Camera</string>
<string name="interact_live_video_stream_type_screen">Screen</string>
<string name="interact_live_next">Next</string>
<string name="interact_live_user_id">User ID</string>
<string name="interact_live_room_id">Room ID</string>
<string name="app_info">APP Info</string>
<string name="app_info_app_id">App ID</string>
<string name="app_info_app_key">App Key</string>
<string name="app_info_play_domain">PlayDomain</string>
<string name="interact_live_user_id_hint">Please enter letters and numbers</string>
<string name="interact_live_anchor">anchor</string>
<string name="interact_live_audience">audience</string>
<string name="interact_input_limit">0/5</string>
<string name="interact_start_connect">Connect</string>
<string name="pk_start_connect">PK</string>
<string name="interact_stop_connect">UnConnect</string>
<string name="pk_stop_connect">EndPK</string>
<string name="interact_live_connecting">Connecting</string>
<string name="pk_live_connecting">Connecting</string>
<string name="interact_live_connect_tips">Enter the User ID</string>
<string name="interact_live_url_connect_tips">Enter the User URL</string>
<string name="interact_live_connect_author_tips">Enter the author ID</string>
<string name="interact_live_connect_room_tips">Enter the room ID</string>
<string name="interact_live_connect_input_error_tips">Input content</string>
<string name="interact_live_connect_finish_tips">End connect</string>
<string name="pk_live_connect_finish_tips">End PK</string>
<string name="interact_live_leave_room_tips">Leave room</string>
<string name="interact_live_viewer_left">Viewer Left</string>
<string name="interact_live_lost_connect">Disconnect the link, please re-enter the live room</string>
<string name="extern_stream_main">Main Extern Stream</string>
<string name="beauty_switch">Beauty</string>
<string name="waiting_download_video_resources">waiting for download external video resources</string>
<string name="interact_live_setting_resolution">Resolution</string>
<string name="target_bitrate">Target bitrate</string>
<string name="target_rate_value">1200</string>
<string name="captrue_fps">Captrue fps</string>
<string name="init_fps">20</string>
<string name="min_fps">Min fps</string>
<string name="live_push_switch_to_facing_back_camera">Switch to facing back camera,before scanning code</string>
<string name="tips_use_url_for_interactive">Use URL for interactive live</string>
<string name="tips_interactive_url_use_different_sdkappid">The sdkAppId used in the URL is different from the one in the App Info. Please note that this may cause pulling stream failure!</string>
</resources>

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_interact_name_tv">连麦互动</string>
<string name="pull_interact_name_url">推拉裸流</string>
<string name="pull_multi_interact_name_tv">多人连麦/PK</string>
<string name="h5_compatible_name_tv">H5兼容模式</string>
<string name="earback_open_without_headset">强制耳返</string>
<string name="data_channel_name_tv">DataChannel消息通道</string>
<string name="external_audio_stream_tv">自定义音频</string>
<string name="external_video_stream_tv">自定义视频</string>
<string name="dual_audio_stream">推音频辅流</string>
<string name="screen_share_stream">推屏幕共享流</string>
<string name="change_resolution_and_fps">切换分辨率与FPS</string>
<string name="force_ear_monitor">强制耳返</string>
<string name="push_interactive_setting_audio_only">纯音频</string>
<string name="push_interactive_setting_gop">GOP</string>
<string name="push_interactive_setting_hardware_decode">开启硬解</string>
<string name="push_interactive_h265">H265</string>
<string name="interact_live">连麦互动</string>
<string name="interact_live_url_title">推拉裸流配置</string>
<string name="interact_live_push_url">请输入推流地址</string>
<string name="interact_live_pull_url">请输入拉流地址</string>
<string name="interact_start_push">开始推流</string>
<string name="interact_stop_push">停止推流</string>
<string name="pk_live">PK互动</string>
<string name="interact_setting">参数设置</string>
<string name="interact_live_cancel">取消</string>
<string name="interact_live_confirm">确认</string>
<string name="interact_live_audio_stream_type">音频流类型</string>
<string name="interact_live_audio_stream_type_mic">麦克风流</string>
<string name="interact_live_audio_stream_type_dual">音频辅流</string>
<string name="interact_live_video_stream_type">视频流类型</string>
<string name="interact_live_video_stream_type_camera">摄像头流</string>
<string name="interact_live_video_stream_type_screen">屏幕共享</string>
<string name="interact_live_next">下一步</string>
<string name="interact_live_user_id">用户 ID</string>
<string name="interact_live_room_id">房间号</string>
<string name="app_info">应用信息</string>
<string name="app_info_app_id">App ID</string>
<string name="app_info_app_key">App Key</string>
<string name="app_info_play_domain">播流域名</string>
<string name="interact_live_user_id_hint">请输入字母、数字</string>
<string name="interact_live_anchor">主播</string>
<string name="interact_live_audience">观众</string>
<string name="interact_input_limit">0/5</string>
<string name="interact_start_connect">开始连麦</string>
<string name="pk_start_connect">开始PK</string>
<string name="interact_stop_connect">结束连麦</string>
<string name="pk_stop_connect">结束PK</string>
<string name="interact_live_connect_tips">请输入观众的用户ID</string>
<string name="interact_live_url_connect_tips">请输入连麦观众的 URL</string>
<string name="interact_live_connect_author_tips">请输入主播的用户ID</string>
<string name="interact_live_connect_room_tips">请输入主播的房间号</string>
<string name="interact_live_connect_input_error_tips">请输入内容</string>
<string name="interact_live_connecting">正在连麦</string>
<string name="pk_live_connecting">正在PK</string>
<string name="interact_live_connect_finish_tips">确认要结束本次连麦吗?</string>
<string name="pk_live_connect_finish_tips">确认要结束本次 PK 吗?</string>
<string name="interact_live_leave_room_tips">确认要退出房间吗?</string>
<string name="interact_live_viewer_left">观众已离开,结束连麦</string>
<string name="interact_live_lost_connect">断开链接,请重新进入直播间</string>
<string name="extern_stream_main">外部音视频</string>
<string name="beauty_switch">美颜</string>
<string name="waiting_download_video_resources">正在下载外部音视频资源中,请等待</string>
<string name="interact_live_setting_resolution">分辨率</string>
<string name="target_bitrate">视频目标码率</string>
<string name="target_rate_value">1200</string>
<string name="captrue_fps">采集帧率</string>
<string name="init_fps">20</string>
<string name="min_fps">最小帧率</string>
<string name="live_push_switch_to_facing_back_camera">扫码前请切换到后置摄像头</string>
<string name="tips_use_url_for_interactive">使用URL方式进行互动直播</string>
<string name="tips_interactive_url_use_different_sdkappid">URL中使用的sdkAppId与应用信息中的不同请注意这可能会导致拉流失败</string>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="InteractLiveRadioButton">
<attr name="ilrb_text" format="string" />
</declare-styleable>
<declare-styleable name="InteractiveQrEditView">
<attr name="title" format="string" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="wheel_black">#000000</color>
<color name="transparent">#50000000</color>
<color name="alivc_color_black">#000</color>
<color name="shape_red_rectangle">#F53F3F</color>
</resources>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_interact_name_tv">Connect Interact</string>
<string name="pull_interact_name_url">Connect Input URL</string>
<string name="pull_multi_interact_name_tv">Multi Connect Interact/PK</string>
<string name="h5_compatible_name_tv">H5 Compatible</string>
<string name="earback_open_without_headset">Earback Without Headset</string>
<string name="data_channel_name_tv">Data Channel</string>
<string name="external_audio_stream_tv">External Audio Stream</string>
<string name="external_video_stream_tv">External Video Stream</string>
<string name="dual_audio_stream">Push Dual Audio</string>
<string name="screen_share_stream">Push Screen Share</string>
<string name="change_resolution_and_fps">Change Resolution FPS</string>
<string name="force_ear_monitor">Force Ear Monitor</string>
<string name="push_interactive_setting_audio_only">Audio Only</string>
<string name="push_interactive_setting_gop">GOP</string>
<string name="push_interactive_setting_hardware_decode">Enable Hardware Decode</string>
<string name="push_interactive_h265">H265</string>
<string name="interact_live">Interact Live</string>
<string name="interact_live_url_title">Interact Live URL</string>
<string name="interact_live_push_url">Interact Live PUSH URL</string>
<string name="interact_live_pull_url">Interact Live PULL URL</string>
<string name="interact_start_push">Start Push</string>
<string name="interact_stop_push">Stop Push</string>
<string name="pk_live">PK Interaction</string>
<string name="interact_setting">Interact Setting</string>
<string name="interact_live_cancel">Cancel</string>
<string name="interact_live_confirm">Confirm</string>
<string name="interact_live_audio_stream_type">Audio Stream</string>
<string name="interact_live_audio_stream_type_mic">Mic</string>
<string name="interact_live_audio_stream_type_dual">Dual</string>
<string name="interact_live_video_stream_type">Video Stream</string>
<string name="interact_live_video_stream_type_camera">Camera</string>
<string name="interact_live_video_stream_type_screen">Screen</string>
<string name="interact_live_next">Next</string>
<string name="interact_live_user_id">User ID</string>
<string name="interact_live_room_id">Room ID</string>
<string name="app_info">APP Info</string>
<string name="app_info_app_id">App ID</string>
<string name="app_info_app_key">App Key</string>
<string name="app_info_play_domain">PlayDomain</string>
<string name="interact_live_user_id_hint">Please enter letters and numbers</string>
<string name="interact_live_anchor">anchor</string>
<string name="interact_live_audience">audience</string>
<string name="interact_input_limit">0/5</string>
<string name="interact_start_connect">Connect</string>
<string name="pk_start_connect">PK</string>
<string name="interact_stop_connect">UnConnect</string>
<string name="pk_stop_connect">EndPK</string>
<string name="interact_live_connecting">Connecting</string>
<string name="pk_live_connecting">Connecting</string>
<string name="interact_live_connect_tips">Enter the User ID</string>
<string name="interact_live_url_connect_tips">Enter the User URL</string>
<string name="interact_live_connect_author_tips">Enter the author ID</string>
<string name="interact_live_connect_room_tips">Enter the room ID</string>
<string name="interact_live_connect_input_error_tips">Input content</string>
<string name="interact_live_connect_finish_tips">End connect</string>
<string name="pk_live_connect_finish_tips">End PK</string>
<string name="interact_live_leave_room_tips">Leave room</string>
<string name="interact_live_viewer_left">Viewer Left</string>
<string name="interact_live_lost_connect">Disconnect the link, please re-enter the live room</string>
<string name="extern_stream_main">Main Extern Stream</string>
<string name="beauty_switch">Beauty</string>
<string name="waiting_download_video_resources">waiting for download external video resources</string>
<string name="interact_live_setting_resolution">Resolution</string>
<string name="target_bitrate">Target bitrate</string>
<string name="target_rate_value">1200</string>
<string name="captrue_fps">Captrue fps</string>
<string name="init_fps">20</string>
<string name="min_fps">Min fps</string>
<string name="live_push_switch_to_facing_back_camera">Switch to facing back camera,before scanning code</string>
<string name="tips_use_url_for_interactive">Use URL for interactive live</string>
<string name="tips_interactive_url_use_different_sdkappid">The sdkAppId used in the URL is different from the one in the App Info. Please note that this may cause pulling stream failure!</string>
</resources>

View File

@@ -0,0 +1,12 @@
<resources>
<style name="AUIEditText">
<item name="colorControlNormal">#3A3D48</item>
<item name="colorControlActivated">#3A3D48</item>
</style>
<style name="Push_SeekBar_Style"
parent="Widget.AppCompat.SeekBar">
<item name="colorAccent">#4DCFE1</item>
</style>
</resources>

1
LiveInteractive/live_pk/.gitignore vendored Normal file
View File

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

View File

@@ -0,0 +1,43 @@
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 :'interactivePK' ]
}
}
}
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 externalARouter
annotationProcessor externalARouterCompiler
implementation project(':LiveInteractive:live_interactive_common')
}

View File

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

View File

@@ -0,0 +1,478 @@
package com.alivc.live.interactive_pk;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alivc.live.commonbiz.testapi.EGLContextTest;
import com.alivc.live.commonui.messageview.AutoScrollMessagesView;
import com.alivc.live.commonui.utils.StatusBarUtil;
import com.alivc.live.commonui.widgets.ResizableFrameLayout;
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.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.InteractiveRoomControlView;
import com.alivc.live.interactive_common.widget.RoomAndUserInfoView;
import com.alivc.live.player.annotations.AlivcLivePlayError;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 多人 PK 互动界面
*/
@Route(path = "/interactivePK/multiPKLive")
public class MultiPKLiveActivity extends AppCompatActivity {
private static final String TAG = "MultiPKLiveActivity";
private boolean isScene16In = false;
private PKController mPKController;
private AUILiveDialog mAUILiveDialog;
private InteractLiveIntent mCurrentIntent;
private ImageView mCloseImageView;
private TextView mShowConnectTextView;
private ResizableFrameLayout mOwnerFrameLayout;
private RoomAndUserInfoView mOwnerInfoView;
private RecyclerView mRecyclerView;
private TextView mHomeIdTextView;
private MultiPKLiveRecyclerViewAdapter mMultiPKLiveRecyclerViewAdapter;
private List<Boolean> mDataList = new ArrayList<>();
private Map<String, Integer> mKeyPositionMap = new HashMap<>();
private Map<String, InteractiveUserData> mUserDataMap = new HashMap<>();
//当 item 划出屏幕时,防止 stop 事件调用导致 mKeyPositionMap 移除对应的 userKey从而无法 resumeVideo
private List<String> mDetachedUserKeyList = new ArrayList<>();
//已经添加混流的 userId
private List<String> mHasAddMixStreamUserId = new ArrayList<>();
//停止 PK 时需要的 roomId 和 userId
private InteractiveUserData mStopPKUserData;
private ConnectionLostTipsView mConnectionLostTipsView;
private InteractiveRoomControlView mInteractiveRoomControlView;
private AutoScrollMessagesView mSeiMessageView;
private InteractiveUserData mAnchorUserData;
@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_pklive);
// 是否是全屏16方场景
isScene16In = getIntent().getBooleanExtra(InteractiveConstants.KEY_TYPE_SCENE_MULTI_PK_16IN, false);
initView();
initListener();
initData();
}
private void initView() {
mAUILiveDialog = new AUILiveDialog(this);
mCloseImageView = findViewById(R.id.iv_close);
mOwnerFrameLayout = findViewById(R.id.frame_owner);
if (isScene16In) {
int widthPixel = dip2px(getBaseContext(), MultiPKLiveRecyclerViewAdapter.ITEM_CONTAINER_WIDTH_16IN);
int heightPixel = dip2px(getBaseContext(), MultiPKLiveRecyclerViewAdapter.ITEM_CONTAINER_WIDTH_16IN / 9 * 16);
ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(widthPixel, heightPixel);
mOwnerFrameLayout.setLayoutParams(layoutParams);
}
mOwnerInfoView = findViewById(R.id.owner_info_view);
mShowConnectTextView = findViewById(R.id.tv_show_connect);
mHomeIdTextView = findViewById(R.id.tv_home_id);
mHomeIdTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Thread thread = new Thread(() -> EGLContextTest.testGLContext());
mSeiMessageView.appendMessage("Manual Create EGLContext: " + thread.getName());
thread.start();
}
});
mConnectionLostTipsView = new ConnectionLostTipsView(this);
mInteractiveRoomControlView = findViewById(R.id.interactive_setting_view);
mSeiMessageView = findViewById(R.id.sei_receive_view);
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, isScene16In ? 4 : 2);
gridLayoutManager.setOrientation(GridLayoutManager.HORIZONTAL);
mRecyclerView = findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(gridLayoutManager);
mMultiPKLiveRecyclerViewAdapter = new MultiPKLiveRecyclerViewAdapter(isScene16In);
for (int i = 0; i < 16; i++) {
mDataList.add(false);
}
mMultiPKLiveRecyclerViewAdapter.setData(mDataList);
mRecyclerView.setAdapter(mMultiPKLiveRecyclerViewAdapter);
}
private void initListener() {
mInteractiveRoomControlView.setOnClickEventListener(new InteractiveRoomControlView.OnClickEventListener() {
@Override
public void onClickSwitchCamera() {
mPKController.switchCamera();
}
@Override
public void onClickSpeakerPhone(boolean enable) {
mPKController.enableSpeakerPhone(enable);
}
@Override
public void onClickMuteAudio(boolean mute) {
mPKController.setMute(mute);
}
@Override
public void onClickEnableAudio(boolean enable) {
mPKController.enableAudioCapture(enable);
}
@Override
public void onClickMuteVideo(boolean mute) {
mPKController.muteLocalCamera(mute);
}
@Override
public void onClickEnableVideo(boolean enable) {
mPKController.enableLocalCamera(enable);
}
});
mConnectionLostTipsView.setConnectionLostListener(new ConnectionLostListener() {
@Override
public void onConfirm() {
runOnUiThread(new Runnable() {
@Override
public void run() {
finish();
}
});
}
});
mCloseImageView.setOnClickListener(view -> {
mCurrentIntent = InteractLiveIntent.INTENT_FINISH;
showInteractLiveDialog(0, getResources().getString(R.string.interact_live_leave_room_tips), false);
});
mMultiPKLiveRecyclerViewAdapter.setOnPKItemViewChangedListener(new MultiPKLiveRecyclerViewAdapter.OnPKItemViewChangedListener() {
@Override
public void onItemViewAttachedToWindow(int position) {
String userKey = getUserKeyByPosition(position);
if (TextUtils.isEmpty(userKey)) {
return;
}
mDetachedUserKeyList.remove(userKey);
InteractiveUserData userData = mUserDataMap.get(userKey);
if (userData == null) {
return;
}
MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder viewHolder = (MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder) mRecyclerView.findViewHolderForAdapterPosition(position);
if (viewHolder != null) {
boolean reCreated = mMultiPKLiveRecyclerViewAdapter.resetFrameLayout(position, viewHolder.getRenderFrameLayout().hashCode());
if (reCreated) {
mPKController.setPullView(userData, viewHolder.getRenderFrameLayout());
}
}
Log.d(TAG, "onItemViewAttachedToWindow: userKey = " + userKey + " --- position = " + position);
mPKController.resumeVideoPlaying(userKey);
}
@Override
public void onItemViewDetachedToWindow(int position) {
String userKey = getUserKeyByPosition(position);
if (TextUtils.isEmpty(userKey)) {
return;
}
mDetachedUserKeyList.add(userKey);
Log.d(TAG, "onItemViewDetachedToWindow: userKey = " + userKey + " --- position = " + position);
mPKController.pauseVideoPlaying(userKey);
}
});
mMultiPKLiveRecyclerViewAdapter.setOnPKConnectClickListener(new MultiPKLiveRecyclerViewAdapter.OnPKItemClickListener() {
@Override
public void onPKConnectClick(int position) {
String content = getUserKeyByPosition(position);
mStopPKUserData = mUserDataMap.get(content);
if (mStopPKUserData != null && mPKController.isPKing(mStopPKUserData)) {
mCurrentIntent = InteractLiveIntent.INTENT_STOP_PULL;
showInteractLiveDialog(position, getResources().getString(R.string.pk_live_connect_finish_tips), false);
} else {
showInteractLiveDialog(position, null, true);
}
}
@Override
public void onPKMuteClick(int position, boolean mute) {
String userKey = getUserKeyByPosition(position);
mPKController.mutePKAnchor(userKey, mute);
}
@Override
public void onEmptyView(int position, boolean empty) {
String userKey = getUserKeyByPosition(position);
InteractiveUserData userData = mUserDataMap.get(userKey);
if (empty) {
mPKController.setPullView(userData, null);
} else {
MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder viewHolder = (MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder) mRecyclerView.findViewHolderForAdapterPosition(position);
mPKController.setPullView(userData, viewHolder != null ? viewHolder.getRenderFrameLayout() : null);
}
}
});
}
private void initData() {
InteractiveUserData anchorUserData = (InteractiveUserData) getIntent().getSerializableExtra(InteractiveConstants.DATA_TYPE_INTERACTIVE_USER_DATA);
mPKController = new PKController(this, anchorUserData);
mAnchorUserData = anchorUserData;
mPKController.setMultiPKLivePushPullListener(new InteractLivePushPullListener() {
@Override
public void onPullSuccess(InteractiveUserData userData) {
super.onPullSuccess(userData);
if (userData == null) {
return;
}
String userKey = userData.getKey();
//开始 PK 成功
if (mKeyPositionMap.containsKey(userKey)) {
MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder viewHolder = (MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder) mRecyclerView.findViewHolderForAdapterPosition(mKeyPositionMap.get(userKey));
if (viewHolder != null && !mHasAddMixStreamUserId.contains(userData.userId)) {
viewHolder.updateUserInfo(userData.channelId, userData.userId);
mHasAddMixStreamUserId.add(userData.userId);
//添加混流
mPKController.addMultiPKLiveMixTranscoding(false, userData, viewHolder.getRenderFrameLayout());
}
changeFrameLayoutViewVisible(true, mKeyPositionMap.get(userKey));
}
}
@Override
public void onPullError(InteractiveUserData userData, AlivcLivePlayError errorType, String errorMsg) {
super.onPullError(userData, errorType, errorMsg);
if (userData == null) {
return;
}
if (errorType == AlivcLivePlayError.AlivcLivePlayErrorStreamStopped) {
ToastUtils.show("用户号:" + userData.userId + " 房间号:" + userData.channelId + " 主播离开");
}
}
@Override
public void onPullStop(InteractiveUserData userData) {
super.onPullStop(userData);
if (userData == null) {
return;
}
String userKey = userData.getKey();
//停止 PK
if (mHasAddMixStreamUserId.contains(userData.userId)) {
//删除对应的混流
mHasAddMixStreamUserId.remove(userData.userId);
mPKController.removeMultiPKLiveMixTranscoding(false, userData);
if (mKeyPositionMap.containsKey(userKey) && mKeyPositionMap.get(userKey) != null) {
Integer position = mKeyPositionMap.get(userKey);
MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder viewHolder = (MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder) mRecyclerView.findViewHolderForAdapterPosition(position);
if (viewHolder != null) {
viewHolder.getRenderFrameLayout().removeAllViews();
}
}
}
if (mKeyPositionMap.containsKey(userKey) && !mDetachedUserKeyList.contains(userKey)) {
changeFrameLayoutViewVisible(false, mKeyPositionMap.get(userKey));
}
mKeyPositionMap.remove(userKey);
}
@Override
public void onConnectionLost() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mConnectionLostTipsView.show();
}
});
}
@Override
public void onPushSuccess() {
super.onPushSuccess();
}
@Override
public void onPushError() {
super.onPushError();
}
@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]");
}
});
mPKController.startPush(mOwnerFrameLayout);
mPKController.addMultiPKLiveMixTranscoding(true, mPKController.mOwnerUserData, null);
mHomeIdTextView.setText(anchorUserData.channelId);
mOwnerInfoView.setUserData(anchorUserData);
}
private void showInteractLiveDialog(int position, String content, boolean showInputView) {
InteractiveCommonInputView commonInputView = new InteractiveCommonInputView(MultiPKLiveActivity.this);
commonInputView.setViewType(InteractiveCommonInputView.ViewType.PK);
commonInputView.showInputView(content, showInputView);
commonInputView.setDefaultRoomId(mAnchorUserData != null ? mAnchorUserData.channelId : "");
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_FINISH) {
finish();
} else if (mCurrentIntent == InteractLiveIntent.INTENT_STOP_PULL) {
mAUILiveDialog.dismiss();
if (mStopPKUserData != null) {
mPKController.stopMultiPK(mStopPKUserData);
}
changeFrameLayoutViewVisible(false, position);
}
}
@Override
public void onInputConfirm(InteractiveUserData userData) {
mAUILiveDialog.dismiss();
if (userData == null || TextUtils.isEmpty(userData.channelId) || TextUtils.isEmpty(userData.userId)) {
return;
}
String userDataKey = userData.getKey();
if (mKeyPositionMap.containsKey(userDataKey)) {
return;
}
//同一个用户 id 不能重复 PK。
if (isInPK(userData.userId)) {
return;
}
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager != null) {
layoutManager.scrollToPosition(position);
}
mRecyclerView.post(() -> {
MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder viewHolder = (MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder) mRecyclerView.findViewHolderForAdapterPosition(position);
if (viewHolder != null) {
mKeyPositionMap.put(userDataKey, position);
mUserDataMap.put(userDataKey, userData);
mPKController.setMultiPKOtherInfo(userData);
mPKController.startMultiPK(userData, viewHolder.getRenderFrameLayout());
}
});
}
});
}
private void changeFrameLayoutViewVisible(boolean isShowSurfaceView, int position) {
mDataList.set(position, isShowSurfaceView);
if (isShowSurfaceView) {
mShowConnectTextView.setVisibility(View.VISIBLE);
} else {
mShowConnectTextView.setVisibility(View.GONE);
}
mMultiPKLiveRecyclerViewAdapter.notifyItemChanged(position, "payload");
}
/**
* 判断该用户 id 是否在 PK 中
*
* @param userId 用户 ID
*/
private boolean isInPK(String userId) {
return mKeyPositionMap.containsKey(userId);
}
@Override
protected void onDestroy() {
super.onDestroy();
mPKController.release();
}
private String getUserKeyByPosition(int position) {
String content = "";
for (String key : mKeyPositionMap.keySet()) {
Integer value = mKeyPositionMap.get(key);
if (value != null && position == value) {
content = key;
break;
}
}
return content;
}
/**
* 将dip或dp值转换为px值保证尺寸大小不变
*
* @param dipValue
* @param dipValue DisplayMetrics类中属性density
* @return
*/
private static int dip2px(Context context, float dipValue) {
if (context == null || context.getResources() == null)
return 1;
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
}

View File

@@ -0,0 +1,211 @@
package com.alivc.live.interactive_pk;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
import com.alivc.live.commonui.widgets.ResizableFrameLayout;
import com.alivc.live.interactive_common.widget.InteractivePaneControlView;
import com.alivc.live.interactive_common.widget.RoomAndUserInfoView;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MultiPKLiveRecyclerViewAdapter extends RecyclerView.Adapter<MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder> {
private final Map<Integer, Integer> mFrameLayoutWithPositionMap = new HashMap<>();
private OnPKItemClickListener mListener;
private List<Boolean> mData;
private OnPKItemViewChangedListener mOnPKItemViewChangedListener;
private boolean isScene16In = false;
public static final float ITEM_CONTAINER_WIDTH_16IN = 80f;
public MultiPKLiveRecyclerViewAdapter(boolean scene16In) {
isScene16In = scene16In;
}
public boolean resetFrameLayout(int position, int frameLayoutHashCode) {
if (mFrameLayoutWithPositionMap.containsKey(position)) {
if (frameLayoutHashCode == mFrameLayoutWithPositionMap.get(position)) {
return false;
} else {
mFrameLayoutWithPositionMap.put(position, frameLayoutHashCode);
return true;
}
}
return false;
}
@Override
public void onViewAttachedToWindow(MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder holder) {
super.onViewAttachedToWindow(holder);
if (mOnPKItemViewChangedListener != null) {
mOnPKItemViewChangedListener.onItemViewAttachedToWindow(holder.getAdapterPosition());
}
}
@Override
public void onViewDetachedFromWindow(MultiPKLiveRecyclerViewAdapter.MultiPKLiveViewHolder holder) {
super.onViewDetachedFromWindow(holder);
if (mOnPKItemViewChangedListener != null) {
mOnPKItemViewChangedListener.onItemViewDetachedToWindow(holder.getAdapterPosition());
}
}
@NonNull
@Override
public MultiPKLiveViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_multi_pklive_item, parent, false);
return new MultiPKLiveViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MultiPKLiveViewHolder holder, int position) {
Boolean isPKing = mData.get(position);
holder.getRenderFrameLayout().setVisibility(isPKing ? View.VISIBLE : View.INVISIBLE);
holder.getUnConnectFrameLayout().setVisibility(isPKing ? View.INVISIBLE : View.VISIBLE);
if (isPKing) {
holder.getConnectView().setText(holder.itemView.getContext().getResources().getString(R.string.pk_stop_connect));
holder.getConnectView().setBackground(holder.itemView.getContext().getResources().getDrawable(R.drawable.shape_interact_live_un_connect_btn_bg));
} else {
holder.getConnectView().setText(holder.itemView.getContext().getResources().getString(R.string.pk_start_connect));
holder.getConnectView().setBackground(holder.itemView.getContext().getResources().getDrawable(R.drawable.shape_pysh_btn_bg));
}
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public void setOnPKConnectClickListener(OnPKItemClickListener listener) {
this.mListener = listener;
}
public void setOnPKItemViewChangedListener(OnPKItemViewChangedListener listener) {
this.mOnPKItemViewChangedListener = listener;
}
public void setData(List<Boolean> dataList) {
this.mData = dataList;
}
public class MultiPKLiveViewHolder extends RecyclerView.ViewHolder {
private final RoomAndUserInfoView mUserInfoView;
private InteractivePaneControlView mUserCtrlView;
private final ResizableFrameLayout mOtherFrameLayout;
private final FrameLayout mUnConnectFrameLayout;
private final TextView mConnectTextView;
public MultiPKLiveViewHolder(@NonNull View itemView) {
super(itemView);
mUserInfoView = itemView.findViewById(R.id.view_userinfo);
mUserCtrlView = itemView.findViewById(R.id.view_ctrl);
mUserCtrlView.enableMute(true);
mUserCtrlView.setOnClickEventListener(new InteractivePaneControlView.OnClickEventListener() {
@Override
public void onClickMuteAudio(boolean mute) {
if (mListener != null) {
mListener.onPKMuteClick(getAdapterPosition(), mute);
}
}
@Override
public void onClickEmptyView(boolean empty) {
if (mListener != null) {
mListener.onEmptyView(getAdapterPosition(), empty);
}
}
@Override
public void onClickResize(boolean resize) {
mOtherFrameLayout.resize();
}
});
mOtherFrameLayout = itemView.findViewById(R.id.render_container);
mUnConnectFrameLayout = itemView.findViewById(R.id.fl_un_connect);
mConnectTextView = itemView.findViewById(R.id.tv_connect);
if (isScene16In) {
mConnectTextView.setVisibility(View.GONE);
int widthPixel = dip2px(itemView.getContext(), ITEM_CONTAINER_WIDTH_16IN);
int heightPixel = dip2px(itemView.getContext(), ITEM_CONTAINER_WIDTH_16IN / 9 * 16);
ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(widthPixel, heightPixel);
mOtherFrameLayout.setLayoutParams(layoutParams);
itemView.setOnClickListener(view -> {
connectPK();
});
} else {
mConnectTextView.setOnClickListener(view -> {
connectPK();
});
}
}
public void updateUserInfo(String channelId, String userId) {
mUserInfoView.setUserInfo(channelId, userId);
}
public FrameLayout getRenderFrameLayout() {
return mOtherFrameLayout;
}
public FrameLayout getUnConnectFrameLayout() {
return mUnConnectFrameLayout;
}
public TextView getConnectView() {
return mConnectTextView;
}
private void connectPK() {
if (mListener != null) {
mFrameLayoutWithPositionMap.put(getAdapterPosition(), mOtherFrameLayout.hashCode());
mListener.onPKConnectClick(getAdapterPosition());
}
}
}
/**
* 将dip或dp值转换为px值保证尺寸大小不变
*
* @param dipValue
* @param dipValue DisplayMetrics类中属性density
* @return
*/
private static int dip2px(Context context, float dipValue) {
if (context == null || context.getResources() == null)
return 1;
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
public interface OnPKItemClickListener {
void onPKConnectClick(int position);
void onPKMuteClick(int position, boolean mute);
void onEmptyView(int position, boolean empty);
}
public interface OnPKItemViewChangedListener {
void onItemViewAttachedToWindow(int position);
void onItemViewDetachedToWindow(int position);
}
}

View File

@@ -0,0 +1,470 @@
package com.alivc.live.interactive_pk;
import static com.alivc.live.interactive_common.utils.LivePushGlobalConfig.mAlivcLivePushConfig;
import android.content.Context;
import android.widget.FrameLayout;
import com.alivc.live.annotations.AlivcLiveMuteLocalAudioMode;
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.player.annotations.AlivcLivePlayVideoStreamType;
import com.alivc.live.pusher.AlivcImageFormat;
import com.alivc.live.pusher.AlivcLiveLocalRecordConfig;
import com.alivc.live.pusher.AlivcLivePushAudioEffectVoiceChangeMode;
import com.alivc.live.pusher.AlivcLivePushAudioFrame;
import com.alivc.live.pusher.AlivcLivePushExternalAudioStreamConfig;
import com.alivc.live.pusher.AlivcLivePushVideoConfig;
import com.alivc.live.pusher.AlivcLivePusherRawDataSample;
import com.alivc.live.pusher.AlivcPreviewDisplayMode;
import com.alivc.live.pusher.AlivcResolutionEnum;
import java.io.File;
public class PKController {
private final PKLiveManager mPKLiveManager;
private final Context mContext;
private int mAudioStreamId = -1;
// 主播信息
public InteractiveUserData mOwnerUserData;
// PK主播信息
public InteractiveUserData mOtherUserData;
// 外部音视频输入
private LocalStreamReader mLocalStreamReader;
private LocalStreamReader mLocalStreamReader2;
private LocalStreamReader mLocalStreamReader3;
public PKController(Context context, InteractiveUserData userData) {
mPKLiveManager = new PKLiveManager();
mPKLiveManager.init(context.getApplicationContext(), InteractiveMode.PK);
this.mContext = context;
initExternalStreamAV();
mOwnerUserData = userData;
}
/**
* 设置 PK 主播的信息
*/
public void setPKOtherInfo(InteractiveUserData userData) {
if (userData == null) {
return;
}
mOtherUserData = userData;
mOtherUserData.url = AliLiveStreamURLUtil.generateInteractivePullUrl(userData.channelId, userData.userId);
}
/**
* 开始推流
*
* @param frameLayout 推流画面渲染 View 的 parent ViewGroup
*/
public void startPush(FrameLayout frameLayout) {
externAV();
mPKLiveManager.startPreviewAndPush(mOwnerUserData, frameLayout, true);
}
private void initExternalStreamAV() {
AlivcResolutionEnum externalStreamResolution = AlivcResolutionEnum.RESOLUTION_720P;
int externalStreamWidth = AlivcResolutionEnum.getResolutionWidth(externalStreamResolution, LivePushGlobalConfig.mAlivcLivePushConfig.getLivePushMode());
int externalStreamHeight = AlivcResolutionEnum.getResolutionHeight(externalStreamResolution, LivePushGlobalConfig.mAlivcLivePushConfig.getLivePushMode());
mLocalStreamReader = new LocalStreamReader.Builder()
.setVideoWith(externalStreamWidth)
.setVideoHeight(externalStreamHeight)
.setVideoStride(externalStreamWidth)
.setVideoSize(externalStreamHeight * externalStreamWidth * 3 / 2)
.setVideoRotation(0)
.setAudioSampleRate(44100)
.setAudioChannel(1)
.setAudioBufferSize(2048)
.build();
mLocalStreamReader2 = new LocalStreamReader.Builder()
.setVideoWith(externalStreamWidth)
.setVideoHeight(externalStreamHeight)
.setVideoStride(externalStreamWidth)
.setVideoSize(externalStreamHeight * externalStreamWidth * 3 / 2)
.setVideoRotation(0)
.setAudioSampleRate(44100)
.setAudioChannel(1)
.setAudioBufferSize(2048)
.build();
mLocalStreamReader3 = new LocalStreamReader.Builder()
.setVideoWith(externalStreamWidth)
.setVideoHeight(externalStreamHeight)
.setVideoStride(externalStreamWidth)
.setVideoSize(externalStreamHeight * externalStreamWidth * 3 / 2)
.setVideoRotation(0)
.setAudioSampleRate(44100)
.setAudioChannel(1)
.setAudioBufferSize(2048)
.build();
}
private void externAV() {
if (LivePushGlobalConfig.mAlivcLivePushConfig.isExternMainStream()) {
File yuvFile = ResourcesConst.localCaptureYUVFilePath(mContext);
mLocalStreamReader.readYUVData(yuvFile, (buffer, pts, videoWidth, videoHeight, videoStride, videoSize, videoRotation) -> {
mPKLiveManager.inputStreamVideoData(buffer, videoWidth, videoHeight, videoStride, videoSize, pts, videoRotation);
});
File pcmFile = ResourcesConst.localCapturePCMFilePath(mContext);
mLocalStreamReader.readPCMData(pcmFile, (buffer, length, pts, audioSampleRate, audioChannel) -> {
mPKLiveManager.inputStreamAudioData(buffer, length, audioSampleRate, audioChannel, pts);
});
}
}
public void startExternalVideoStream() {
// 启用外部视频输入源
mAlivcLivePushConfig.setAlivcExternMainImageFormat(AlivcImageFormat.IMAGE_FORMAT_YUVNV12);
mPKLiveManager.setExternalVideoSource(true, false, AlivcLivePlayVideoStreamType.STREAM_CAMERA,
AlivcPreviewDisplayMode.ALIVC_LIVE_PUSHER_PREVIEW_ASPECT_FILL);
File yuvFile = ResourcesConst.localCaptureYUVFilePath(mContext);
mLocalStreamReader2.readYUVData(yuvFile, (buffer, pts, videoWidth, videoHeight, videoStride, videoSize, videoRotation) -> {
mPKLiveManager.inputStreamVideoData(buffer, videoWidth, videoHeight, videoStride, videoSize, pts, videoRotation);
});
}
public void stopExternalVideoStream() {
mPKLiveManager.setExternalVideoSource(false, false, AlivcLivePlayVideoStreamType.STREAM_CAMERA,
AlivcPreviewDisplayMode.ALIVC_LIVE_PUSHER_PREVIEW_ASPECT_FILL);
mLocalStreamReader2.stopYUV();
}
public void startExternalAudioStream() {
// 新增外部音频流
AlivcLivePushExternalAudioStreamConfig externalAudioStreamConfig = new AlivcLivePushExternalAudioStreamConfig();
externalAudioStreamConfig.channels = 1;
externalAudioStreamConfig.sampleRate = 44100;
externalAudioStreamConfig.playoutVolume = 50;
externalAudioStreamConfig.publishVolume = 50;
externalAudioStreamConfig.publishStream = 0; // 指定绑定的音频流 1-第二音频流0-MIC流
mAudioStreamId = mPKLiveManager.addExternalAudioStream(externalAudioStreamConfig);
// 设置是否与麦克风采集音频混合
mPKLiveManager.setMixedWithMic(true);
// 设置外部音频流播放音量
mPKLiveManager.setExternalAudioStreamPlayoutVolume(mAudioStreamId, 30);
// 设置外部音频流推流音量
mPKLiveManager.setExternalAudioStreamPublishVolume(mAudioStreamId, 100);
// 输入外部音频流数据
File pcmFile = ResourcesConst.localCapturePCMFilePath(mContext);
mLocalStreamReader2.readPCMData(pcmFile, (buffer, length, pts, audioSampleRate, audioChannel) -> {
AlivcLivePushAudioFrame audioFrame = new AlivcLivePushAudioFrame();
audioFrame.data = buffer;
audioFrame.numSamples = length / 8;
audioFrame.bytesPerSample = 8;
audioFrame.numChannels = audioChannel;
audioFrame.samplesPerSec = audioSampleRate;
mPKLiveManager.pushExternalAudioStream(mAudioStreamId, audioFrame);
});
}
public void stopExternalAudioStream() {
// 删除外部音频流
mPKLiveManager.removeExternalAudioStream(mAudioStreamId);
mLocalStreamReader2.stopPcm();
}
// 开启第二路音频流推送
public void startDualAudioStream() {
// 开启后第二路音频流推送
mPKLiveManager.startDualAudioStream();
// 新增外部音频流,用于第二路音频流推送,注意 publishStream 必须为1
AlivcLivePushExternalAudioStreamConfig dualAudioStreamConfig = new AlivcLivePushExternalAudioStreamConfig();
dualAudioStreamConfig.channels = 1;
dualAudioStreamConfig.sampleRate = 44100;
dualAudioStreamConfig.playoutVolume = 50;
dualAudioStreamConfig.publishVolume = 50;
dualAudioStreamConfig.publishStream = 1; // 指定绑定的音频流 1-第二音频流0-MIC流
// 添加音频数据源
mAudioStreamId = mPKLiveManager.addExternalAudioStream(dualAudioStreamConfig);
// 输入外部音频流数据
File pcmFile = ResourcesConst.localCapturePCMFilePath(mContext);
mLocalStreamReader3.readPCMData(pcmFile, (buffer, length, pts, audioSampleRate, audioChannel) -> {
AlivcLivePushAudioFrame audioFrame = new AlivcLivePushAudioFrame();
audioFrame.data = buffer;
audioFrame.numSamples = length / 8;
audioFrame.bytesPerSample = 8;
audioFrame.numChannels = audioChannel;
audioFrame.samplesPerSec = audioSampleRate;
// 向第二路音频通道输入音频数据
mPKLiveManager.pushExternalAudioStream(mAudioStreamId, audioFrame);
});
}
// 停止第二路音频流推送
public void stopDualAudioStream() {
// 停止第二路音频流推送
mPKLiveManager.stopDualAudioStream();
// 删除外部音频流
mPKLiveManager.removeExternalAudioStream(mAudioStreamId);
mLocalStreamReader3.stopPcm();
}
// 开始屏幕共享流
public void startScreenShareStream() {
mPKLiveManager.startScreenShare();
mPKLiveManager.setExternalVideoSource(true, false, AlivcLivePlayVideoStreamType.STREAM_SCREEN,
AlivcPreviewDisplayMode.ALIVC_LIVE_PUSHER_PREVIEW_ASPECT_FIT);
File yuvFile = ResourcesConst.localCaptureYUVFilePath(mContext);
mLocalStreamReader3.readYUVData(yuvFile, (buffer, pts, videoWidth, videoHeight, videoStride, videoSize, videoRotation) -> {
AlivcLivePusherRawDataSample rawDataSample = new AlivcLivePusherRawDataSample();
rawDataSample.format = AlivcImageFormat.IMAGE_FORMAT_YUVNV12;
rawDataSample.width = videoWidth;
rawDataSample.height = videoHeight;
rawDataSample.frame = buffer;
rawDataSample.videoFrameLength = videoSize;
rawDataSample.timestamp = pts;
mPKLiveManager.pushExternalVideoFrame(rawDataSample, AlivcLivePlayVideoStreamType.STREAM_SCREEN);
});
}
// 停止屏幕共享流
public void stopScreenShareStream() {
mPKLiveManager.stopScreenShare();
mPKLiveManager.setExternalVideoSource(false, false, AlivcLivePlayVideoStreamType.STREAM_SCREEN,
AlivcPreviewDisplayMode.ALIVC_LIVE_PUSHER_PREVIEW_ASPECT_FIT);
mLocalStreamReader3.stopYUV();
}
/**
* 开始 PK (拉流)
*/
public void startPK(InteractiveUserData userData, FrameLayout frameLayout) {
mPKLiveManager.setPullView(userData, frameLayout, true);
mPKLiveManager.startPullRTCStream(mOtherUserData);
}
/**
* 停止 PK (拉流)
*/
public void stopPK() {
mPKLiveManager.stopPullRTCStream(mOtherUserData);
}
public void setPKLiveMixTranscoding(boolean needMix) {
if (needMix) {
mPKLiveManager.setLiveMixTranscodingConfig(mOwnerUserData, mOtherUserData, false);
} else {
mPKLiveManager.clearLiveMixTranscodingConfig();
}
}
/**
* 静音对端主播一般用于PK惩罚环节的业务场景
*
* @param userKey userKey
* @param mute 是否静音
* @see <a href="https://help.aliyun.com/zh/live/user-guide/configure-mute-in-battle-scenarios">主播PK场景实现静音功能</a>
*/
public void mutePKAnchor(String userKey, boolean mute) {
if (!isPKing() || mPKLiveManager == null) {
return;
}
// 更新连麦静音
if (mute) {
mPKLiveManager.pauseAudioPlaying(userKey);
} else {
mPKLiveManager.resumeAudioPlaying(userKey);
}
// 更新混流静音
mPKLiveManager.muteAnchorMultiStream(mPKLiveManager.getUserDataByKey(userKey), mute);
}
public boolean isPKing() {
return mPKLiveManager.isPulling(mOtherUserData);
}
/**
* 开始 PK (多人 PK 场景)
*/
public void startMultiPK(InteractiveUserData userData, FrameLayout frameLayout) {
if (userData == null) {
return;
}
mPKLiveManager.setPullView(userData, frameLayout, true);
mPKLiveManager.startPullRTCStream(userData);
}
public void setPullView(InteractiveUserData userData, FrameLayout frameLayout) {
mPKLiveManager.setPullView(userData, frameLayout, true);
}
public void updatePreviewView(FrameLayout frameLayout, boolean isFullScreen) {
mPKLiveManager.updatePreview(frameLayout, isFullScreen);
}
/**
* 停止 PK (多人 PK 场景)
*/
public void stopMultiPK(InteractiveUserData userData) {
mPKLiveManager.stopPullRTCStream(userData);
}
/**
* 恢复视频流
*
* @param userKey userKey
*/
public void resumeVideoPlaying(String userKey) {
mPKLiveManager.resumePlayRTCStream(mPKLiveManager.getUserDataByKey(userKey));
}
/**
* 暂停视频流
*
* @param userKey userKey
*/
public void pauseVideoPlaying(String userKey) {
mPKLiveManager.pausePlayRTCStream(mPKLiveManager.getUserDataByKey(userKey));
}
/**
* 设置 PK 主播的信息(多人PK场景)
*/
public void setMultiPKOtherInfo(InteractiveUserData userData) {
setPKOtherInfo(userData);
}
public boolean isPKing(InteractiveUserData userData) {
return mPKLiveManager.isPulling(userData);
}
/**
* 设置混流 (多人 PK 场景)
*
* @param isOwnerUserId 是否是主播自己的 id
* @param userData 主播 userData (如果 isOwnerUserId 为 true则 userId 表示主播自己的 id否则表示 pk 方的主播 id)
* @param frameLayout 当 isOwnerUserId 为 false 时,需要设置 pk 方主播的混流,需要传递 frameLayout 计算混流位置
*/
public void addMultiPKLiveMixTranscoding(boolean isOwnerUserId, InteractiveUserData userData, FrameLayout frameLayout) {
if (isOwnerUserId) {
mPKLiveManager.addAnchorMixTranscodingConfig(userData);
} else {
mPKLiveManager.addAudienceMixTranscodingConfig(userData, frameLayout);
}
}
/**
* 设置混流 (多人 PK 场景)
* 当 isOwnerUserId 为 true 时,表示主播自己退出 PK则删除所有混流设置。否则只删除 PK 方的混流。
*
* @param isOwnerUserId 是否是主播自己的 id
* @param userData 主播 userData (如果 isOwnerUserId 为 true则 userId 表示主播自己的 id否则表示 pk 方的主播 id)
*/
public void removeMultiPKLiveMixTranscoding(boolean isOwnerUserId, InteractiveUserData userData) {
if (isOwnerUserId) {
mPKLiveManager.clearLiveMixTranscodingConfig();
} else {
mPKLiveManager.removeLiveMixTranscodingConfig(userData);
}
}
public void setPKLivePushPullListener(InteractLivePushPullListener listener) {
mPKLiveManager.setInteractLivePushPullListener(listener);
}
public void setMultiPKLivePushPullListener(InteractLivePushPullListener listener) {
mPKLiveManager.setInteractLivePushPullListener(listener);
}
public void release() {
mLocalStreamReader.stopYUV();
mLocalStreamReader.stopPcm();
mLocalStreamReader2.stopYUV();
mLocalStreamReader2.stopPcm();
mLocalStreamReader3.stopYUV();
mLocalStreamReader3.stopPcm();
stopExternalAudioStream();
stopExternalVideoStream();
stopDualAudioStream();
mPKLiveManager.release();
}
public void setBGMEarsBack(boolean isOpen) {
mPKLiveManager.setBGMEarsBack(isOpen);
}
public void setAudioEffectVoiceChangeMode(AlivcLivePushAudioEffectVoiceChangeMode voiceChangeMode) {
mPKLiveManager.setAudioEffectVoiceChangeMode(voiceChangeMode);
}
public void playAudioEffects() {
mPKLiveManager.playAudioEffects();
}
public void stopAudioEffect() {
mPKLiveManager.stopAudioEffect();
}
public void setVideoConfig(AlivcLivePushVideoConfig videoConfig) {
mPKLiveManager.setVideoConfig(videoConfig);
}
public void switchCamera() {
mPKLiveManager.switchCamera();
}
public void enableSpeakerPhone(boolean enable) {
mPKLiveManager.enableSpeakerPhone(enable);
}
public void setMute(boolean b) {
mPKLiveManager.setMute(b);
}
public void setMute(boolean isMute, AlivcLiveMuteLocalAudioMode muteLocalAudioMode) {
mPKLiveManager.setMute(isMute, muteLocalAudioMode);
}
public void enableAudioCapture(boolean enable) {
mPKLiveManager.enableAudioCapture(enable);
}
public void muteLocalCamera(boolean muteLocalCamera) {
mPKLiveManager.muteLocalCamera(muteLocalCamera);
}
public void enableLocalCamera(boolean enable) {
mPKLiveManager.enableLocalCamera(enable);
}
public void sendSEI(String text, int payload) {
mPKLiveManager.sendCustomMessage(text, payload);
}
public void startLocalRecord(AlivcLiveLocalRecordConfig recordConfig) {
if (mPKLiveManager != null) {
mPKLiveManager.startLocalRecord(recordConfig);
}
}
public void stopLocalRecord() {
if (mPKLiveManager != null) {
mPKLiveManager.stopLocalRecord();
}
}
}

View File

@@ -0,0 +1,519 @@
package com.alivc.live.interactive_pk;
import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.WindowManager;
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.annotations.AlivcLiveMuteLocalAudioMode;
import com.alivc.live.annotations.AlivcLiveRecordAudioQuality;
import com.alivc.live.annotations.AlivcLiveRecordMediaFormat;
import com.alivc.live.annotations.AlivcLiveRecordStreamType;
import com.alivc.live.commonbiz.ResourcesConst;
import com.alivc.live.commonui.avdialog.AUILiveDialog;
import com.alivc.live.commonui.messageview.AutoScrollMessagesView;
import com.alivc.live.commonui.seiview.LivePusherSEIView;
import com.alivc.live.commonui.utils.StatusBarUtil;
import com.alivc.live.commonui.widgets.LivePushTextSwitch;
import com.alivc.live.commonui.widgets.ResizableFrameLayout;
import com.alivc.live.commonutils.FileUtil;
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.interactive_common.widget.ConnectionLostTipsView;
import com.alivc.live.interactive_common.widget.InteractiveCommonInputView;
import com.alivc.live.interactive_common.widget.InteractivePaneControlView;
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.alivc.live.pusher.AlivcLiveLocalRecordConfig;
import com.alivc.live.pusher.AlivcLivePushAudioEffectVoiceChangeMode;
import com.alivc.live.pusher.AlivcLivePushVideoConfig;
import com.alivc.live.pusher.AlivcResolutionEnum;
import com.alivc.live.pusher.AlivcVideoEncodeGopEnum;
import com.aliyunsdk.queen.menu.QueenBeautyMenu;
import com.aliyunsdk.queen.menu.QueenMenuPanel;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* PK 互动界面
*/
@Route(path = "/interactivePK/pkLive")
public class PKLiveActivity extends AppCompatActivity {
private PKController mPKController;
private AUILiveDialog mAUILiveDialog;
private InteractLiveIntent mCurrentIntent;
private ImageView mCloseImageView;
private TextView mConnectTextView;
private TextView mShowConnectTextView;
private ResizableFrameLayout mOwnerFrameLayout;
private ResizableFrameLayout mOtherFrameLayout;
private FrameLayout mUnConnectFrameLayout;
private ConnectionLostTipsView mConnectionLostTipsView;
private RoomAndUserInfoView mOwnerInfoView;
private InteractivePaneControlView mOwnerCtrlView;
private RoomAndUserInfoView mOtherInfoView;
private InteractivePaneControlView mOtherCtrlView;
private InteractiveRoomControlView mInteractiveRoomControlView;
private AutoScrollMessagesView mSeiMessageView;
private ImageView mBeautyImageView;
// 美颜menu
private QueenBeautyMenu mQueenBeautyMenu;
private QueenMenuPanel mBeautyMenuPanel;
private TextView mHomeIdTextView;
private LivePusherSEIView mSeiView;
private InteractiveUserData mOtherUserData;
private LivePushTextSwitch mShowCustomMessageView;
private LivePushTextSwitch mLocalRecordView;
private LivePushTextSwitch mExternalAudioStreamView;
private LivePushTextSwitch mExternalVideoStreamView;
private LivePushTextSwitch mPushDualAudioView;
private LivePushTextSwitch mPushScreenShareView;
private LivePushTextSwitch mChangeResolutionFPSView;
private LivePushTextSwitch mForceEarMonitorView;
@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_pklive);
initView();
initListener();
initData();
}
private void initView() {
mAUILiveDialog = new AUILiveDialog(this);
mCloseImageView = findViewById(R.id.iv_close);
mConnectTextView = findViewById(R.id.tv_connect);
mOwnerFrameLayout = findViewById(R.id.frame_owner);
mOtherFrameLayout = findViewById(R.id.render_container);
mUnConnectFrameLayout = findViewById(R.id.fl_un_connect);
mShowConnectTextView = findViewById(R.id.tv_show_connect);
mSeiView = findViewById(R.id.sei_view);
mSeiMessageView = findViewById(R.id.sei_receive_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);
mHomeIdTextView = findViewById(R.id.tv_home_id);
mOwnerInfoView = findViewById(R.id.owner_info_view);
mOwnerCtrlView = findViewById(R.id.owner_ctrl_view);
mOwnerCtrlView.setOnClickEventListener(new InteractivePaneControlView.OnClickEventListener() {
@Override
public void onClickMuteAudio(boolean mute) {
mPKController.setMute(mute);
}
@Override
public void onClickEmptyView(boolean empty) {
mPKController.updatePreviewView(empty ? null : mOwnerFrameLayout, true);
}
@Override
public void onClickResize(boolean resize) {
mOwnerFrameLayout.resize();
}
});
mOtherInfoView = findViewById(R.id.other_info_view);
mOtherCtrlView = findViewById(R.id.other_ctrl_view);
mOtherCtrlView.enableMute(true);
mOtherCtrlView.setOnClickEventListener(new InteractivePaneControlView.OnClickEventListener() {
@Override
public void onClickMuteAudio(boolean mute) {
mPKController.mutePKAnchor(mOtherUserData.getKey(), mute);
}
@Override
public void onClickEmptyView(boolean empty) {
mPKController.setPullView(mOtherUserData, empty ? null : mOtherFrameLayout);
}
@Override
public void onClickResize(boolean resize) {
mOtherFrameLayout.resize();
}
});
mInteractiveRoomControlView = findViewById(R.id.interactive_setting_view);
mConnectionLostTipsView = new ConnectionLostTipsView(this);
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);
});
// 本地录制
mLocalRecordView = findViewById(R.id.btn_local_record);
mLocalRecordView.setTextViewText(getString(R.string.local_record_tv));
mLocalRecordView.setOnSwitchToggleListener(isChecked -> {
if (mPKController == null) {
return;
}
if (isChecked) {
AlivcLiveLocalRecordConfig recordConfig = new AlivcLiveLocalRecordConfig();
String dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
recordConfig.storagePath = FileUtil.getExternalCacheFolder(getBaseContext()) + "/" + dateFormat + ".mp4";
recordConfig.streamType = AlivcLiveRecordStreamType.AUDIO_VIDEO;
recordConfig.audioQuality = AlivcLiveRecordAudioQuality.MEDIUM;
recordConfig.mediaFormat = AlivcLiveRecordMediaFormat.MP4;
mPKController.startLocalRecord(recordConfig);
} else {
mPKController.stopLocalRecord();
}
});
mExternalAudioStreamView = findViewById(R.id.btn_external_audio_stream);
mExternalAudioStreamView.setTextViewText(getString(R.string.external_audio_stream_tv));
mExternalAudioStreamView.setOnSwitchToggleListener(isChecked -> {
// 输入外部音频流数据
if (!checkMediaFileExists()) {
return;
}
if (isChecked) {
mPKController.startExternalAudioStream();
} else {
mPKController.stopExternalAudioStream();
}
});
mExternalVideoStreamView = findViewById(R.id.btn_external_video_stream);
mExternalVideoStreamView.setTextViewText(getString(R.string.external_video_stream_tv));
mExternalVideoStreamView.setOnSwitchToggleListener(isChecked -> {
// 输入外部视频流数据
if (!checkMediaFileExists()) {
return;
}
if (isChecked) {
mPKController.startExternalVideoStream();
} else {
mPKController.stopExternalVideoStream();
}
});
mPushDualAudioView = findViewById(R.id.btn_push_dual_audio);
mPushDualAudioView.setTextViewText(getString(R.string.dual_audio_stream));
mPushDualAudioView.setOnSwitchToggleListener(isChecked -> {
if (!checkMediaFileExists()) {
return;
}
if (isChecked) {
mPKController.startDualAudioStream();
} else {
mPKController.stopDualAudioStream();
}
});
mPushScreenShareView = findViewById(R.id.btn_push_screen_share);
mPushScreenShareView.setTextViewText(getString(R.string.screen_share_stream));
mPushScreenShareView.setOnSwitchToggleListener(isChecked -> {
if (!checkMediaFileExists()) {
return;
}
if (isChecked) {
mPKController.startScreenShareStream();
} else {
mPKController.stopScreenShareStream();
}
});
mChangeResolutionFPSView = findViewById(R.id.btn_change_resolution_and_fps);
mChangeResolutionFPSView.setTextViewText(getString(R.string.change_resolution_and_fps));
mChangeResolutionFPSView.setOnSwitchToggleListener(isChecked -> {
if (isChecked) {
AlivcLivePushVideoConfig videoConfig = new AlivcLivePushVideoConfig();
videoConfig.resolution = AlivcResolutionEnum.RESOLUTION_540P;
videoConfig.initialBitrate = 1200;
videoConfig.targetBitrate = 1200;
videoConfig.minBitrate = 300;
videoConfig.fps = 15;
videoConfig.gop = AlivcVideoEncodeGopEnum.GOP_TWO;
mPKController.setVideoConfig(videoConfig);
} else {
AlivcLivePushVideoConfig videoConfig = new AlivcLivePushVideoConfig();
videoConfig.resolution = AlivcResolutionEnum.RESOLUTION_720P;
videoConfig.initialBitrate = 1800;
videoConfig.targetBitrate = 1800;
videoConfig.minBitrate = 300;
videoConfig.fps = 20;
videoConfig.gop = AlivcVideoEncodeGopEnum.GOP_TWO;
mPKController.setVideoConfig(videoConfig);
}
});
mForceEarMonitorView = findViewById(R.id.btn_force_ear_monitor);
mForceEarMonitorView.setTextViewText(getString(R.string.force_ear_monitor));
mForceEarMonitorView.setOnSwitchToggleListener(isChecked -> {
if (isChecked) {
mPKController.setMute(true, AlivcLiveMuteLocalAudioMode.MUTE_ONLY_MIC);
mPKController.setAudioEffectVoiceChangeMode(AlivcLivePushAudioEffectVoiceChangeMode.SOUND_EFFECT_BABY_BOY);
mPKController.setBGMEarsBack(true);
} else {
mPKController.setMute(false, AlivcLiveMuteLocalAudioMode.MUTE_ONLY_MIC);
mPKController.setAudioEffectVoiceChangeMode(AlivcLivePushAudioEffectVoiceChangeMode.SOUND_EFFECT_OFF);
mPKController.setBGMEarsBack(false);
}
});
}
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();
}
}
});
mSeiView.setSendSeiViewListener((payload, text) -> mPKController.sendSEI(text, payload));
mConnectionLostTipsView.setConnectionLostListener(new ConnectionLostListener() {
@Override
public void onConfirm() {
runOnUiThread(new Runnable() {
@Override
public void run() {
finish();
}
});
}
});
mInteractiveRoomControlView.setOnClickEventListener(new InteractiveRoomControlView.OnClickEventListener() {
@Override
public void onClickSwitchCamera() {
mPKController.switchCamera();
}
@Override
public void onClickSpeakerPhone(boolean enable) {
mPKController.enableSpeakerPhone(enable);
}
@Override
public void onClickMuteAudio(boolean mute) {
mPKController.setMute(mute);
}
@Override
public void onClickEnableAudio(boolean enable) {
mPKController.enableAudioCapture(enable);
}
@Override
public void onClickMuteVideo(boolean mute) {
mPKController.muteLocalCamera(mute);
}
@Override
public void onClickEnableVideo(boolean enable) {
mPKController.enableLocalCamera(enable);
}
});
mCloseImageView.setOnClickListener(view -> {
mCurrentIntent = InteractLiveIntent.INTENT_FINISH;
showInteractLiveDialog(getResources().getString(R.string.interact_live_leave_room_tips), false);
});
//开始 PK
mConnectTextView.setOnClickListener(view -> {
if (mPKController.isPKing()) {
mCurrentIntent = InteractLiveIntent.INTENT_STOP_PULL;
showInteractLiveDialog(getResources().getString(R.string.pk_live_connect_finish_tips), false);
} else {
showInteractLiveDialog(null, true);
}
});
}
private void initData() {
InteractiveUserData anchorUserData = (InteractiveUserData) getIntent().getSerializableExtra(InteractiveConstants.DATA_TYPE_INTERACTIVE_USER_DATA);
mPKController = new PKController(this, anchorUserData);
mPKController.setPKLivePushPullListener(new InteractLivePushPullListener() {
@Override
public void onPullSuccess(InteractiveUserData userData) {
super.onPullSuccess(userData);
mPKController.setPKLiveMixTranscoding(true);
changeFrameLayoutViewVisible(true);
updateConnectTextView(true);
}
@Override
public void onPullError(InteractiveUserData userData, AlivcLivePlayError errorType, String errorMsg) {
super.onPullError(userData, errorType, errorMsg);
}
@Override
public void onPullStop(InteractiveUserData userData) {
super.onPullStop(userData);
mPKController.setPKLiveMixTranscoding(false);
changeFrameLayoutViewVisible(false);
updateConnectTextView(false);
}
@Override
public void onPushSuccess() {
super.onPushSuccess();
}
@Override
public void onPushError() {
super.onPushError();
}
@Override
public void onConnectionLost() {
super.onConnectionLost();
// 断网后停止本地录制
mPKController.stopLocalRecord();
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]");
}
});
mPKController.startPush(mOwnerFrameLayout);
mOwnerInfoView.setUserData(anchorUserData);
mHomeIdTextView.setText(anchorUserData.channelId);
}
private void showInteractLiveDialog(String content, boolean showInputView) {
InteractiveCommonInputView interactiveCommonInputView = new InteractiveCommonInputView(PKLiveActivity.this);
interactiveCommonInputView.setViewType(InteractiveCommonInputView.ViewType.PK);
interactiveCommonInputView.showInputView(content, showInputView);
mAUILiveDialog.setContentView(interactiveCommonInputView);
mAUILiveDialog.show();
interactiveCommonInputView.setOnInteractLiveTipsViewListener(new InteractLiveTipsViewListener() {
@Override
public void onCancel() {
if (mAUILiveDialog.isShowing()) {
mAUILiveDialog.dismiss();
}
}
@Override
public void onConfirm() {
//退出直播
if (mCurrentIntent == InteractLiveIntent.INTENT_FINISH) {
finish();
} else if (mCurrentIntent == InteractLiveIntent.INTENT_STOP_PULL) {
mAUILiveDialog.dismiss();
mPKController.stopPK();
mOtherUserData = null;
updateConnectTextView(false);
changeFrameLayoutViewVisible(false);
}
}
@Override
public void onInputConfirm(InteractiveUserData userData) {
mAUILiveDialog.dismiss();
if (userData != null && !TextUtils.isEmpty(userData.userId) && !TextUtils.isEmpty(userData.channelId)) {
mPKController.setPKOtherInfo(userData);
mPKController.startPK(userData, mOtherFrameLayout);
mOtherUserData = userData;
mOtherInfoView.setUserInfo(userData.userId, userData.channelId);
}
}
});
}
public void updateConnectTextView(boolean connecting) {
if (connecting) {
mShowConnectTextView.setVisibility(View.VISIBLE);
mConnectTextView.setText(getResources().getString(R.string.pk_stop_connect));
mConnectTextView.setBackground(getResources().getDrawable(R.drawable.shape_interact_live_un_connect_btn_bg));
} else {
mShowConnectTextView.setVisibility(View.GONE);
mConnectTextView.setText(getResources().getString(R.string.pk_start_connect));
mConnectTextView.setBackground(getResources().getDrawable(R.drawable.shape_pysh_btn_bg));
}
}
private void changeFrameLayoutViewVisible(boolean isShowSurfaceView) {
mOtherFrameLayout.setVisibility(isShowSurfaceView ? View.VISIBLE : View.INVISIBLE);
mUnConnectFrameLayout.setVisibility(isShowSurfaceView ? View.INVISIBLE : View.VISIBLE);
}
@Override
protected void onDestroy() {
super.onDestroy();
mPKController.release();
}
private boolean checkMediaFileExists() {
File yuvFile = ResourcesConst.localCaptureYUVFilePath(getBaseContext());
if (!yuvFile.exists()) {
ToastUtils.show("Please ensure that the local yuv files have been downloaded!");
return false;
}
File pcmFile = ResourcesConst.localCapturePCMFilePath(getBaseContext());
if (!pcmFile.exists()) {
ToastUtils.show("Please ensure that the local pcm files have been downloaded!");
return false;
}
return true;
}
}

View File

@@ -0,0 +1,204 @@
package com.alivc.live.interactive_pk;
import static com.alivc.live.interactive_common.utils.LivePushGlobalConfig.mAlivcLivePushConfig;
import android.text.TextUtils;
import android.util.Log;
import android.widget.FrameLayout;
import com.alivc.live.interactive_common.InteractLiveBaseManager;
import com.alivc.live.interactive_common.InteractiveBaseUtil;
import com.alivc.live.interactive_common.bean.InteractiveUserData;
import com.alivc.live.player.AlivcLivePlayer;
import com.alivc.live.pusher.AlivcLiveMixStream;
import com.alivc.live.pusher.AlivcLiveMixStreamType;
import com.alivc.live.pusher.AlivcLiveTranscodingConfig;
import java.util.ArrayList;
public class PKLiveManager extends InteractLiveBaseManager {
public void resumeAudioPlaying(String key) {
AlivcLivePlayer alivcLivePlayer = mInteractiveLivePlayerMap.get(key);
if (alivcLivePlayer != null) {
alivcLivePlayer.resumeAudioPlaying();
}
}
public void pauseAudioPlaying(String key) {
AlivcLivePlayer alivcLivePlayer = mInteractiveLivePlayerMap.get(key);
if (alivcLivePlayer != null) {
alivcLivePlayer.pauseAudioPlaying();
}
}
// PK场景下设置混流
public void setLiveMixTranscodingConfig(InteractiveUserData anchorUserData, InteractiveUserData otherUserData, boolean pkMute) {
if (anchorUserData == null || TextUtils.isEmpty(anchorUserData.channelId) || TextUtils.isEmpty(anchorUserData.userId)) {
clearLiveMixTranscodingConfig();
return;
}
if (otherUserData == null || TextUtils.isEmpty(otherUserData.channelId) || TextUtils.isEmpty(otherUserData.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() / 2;
anchorMixStream.height = mAlivcLivePushConfig.getHeight() / 2;
anchorMixStream.zOrder = 1;
anchorMixStream.backgroundImageUrl = "https://alivc-demo-cms.alicdn.com/versionProduct/resources/pictures/siheng.jpg";
mixStreams.add(anchorMixStream);
if (mAudienceFrameLayout != null) {
AlivcLiveMixStream otherMixStream = new AlivcLiveMixStream();
otherMixStream.userId = otherUserData.userId;
otherMixStream.x = mAlivcLivePushConfig.getWidth() / 2;
otherMixStream.y = 0;
otherMixStream.width = mAlivcLivePushConfig.getWidth() / 2;
otherMixStream.height = mAlivcLivePushConfig.getHeight() / 2;
otherMixStream.zOrder = 2;
otherMixStream.mixStreamType = pkMute ? AlivcLiveMixStreamType.PURE_VIDEO : AlivcLiveMixStreamType.AUDIO_VIDEO;
otherMixStream.mixSourceType = InteractiveBaseUtil.covertVideoStreamType2MixSourceType(otherUserData.videoStreamType);
otherMixStream.backgroundImageUrl = "https://alivc-demo-cms.alicdn.com/versionProduct/resources/pictures/lantu.jpg";
mixStreams.add(otherMixStream);
}
AlivcLiveTranscodingConfig transcodingConfig = new AlivcLiveTranscodingConfig();
transcodingConfig.mixStreams = mixStreams;
mAlivcLivePusher.setLiveMixTranscodingConfig(transcodingConfig);
}
// 多人PK场景下添加混流
public void addAnchorMixTranscodingConfig(InteractiveUserData anchorUserData) {
if (anchorUserData == null || TextUtils.isEmpty(anchorUserData.channelId) || TextUtils.isEmpty(anchorUserData.userId)) {
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 muteAnchorMultiStream(InteractiveUserData audienceUserData, boolean mute) {
if (audienceUserData == null || TextUtils.isEmpty(audienceUserData.channelId) || TextUtils.isEmpty(audienceUserData.userId)) {
return;
}
AlivcLiveMixStream mixStream = findMixStreamByUserData(audienceUserData);
if (mixStream == null) {
return;
}
mixStream.mixStreamType = mute ? AlivcLiveMixStreamType.PURE_VIDEO : AlivcLiveMixStreamType.AUDIO_VIDEO;
if (mAlivcLivePusher != null) {
mAlivcLivePusher.setLiveMixTranscodingConfig(mMixInteractLiveTranscodingConfig);
}
}
/**
* 添加混流配置
*
* @param audienceUserData 观众信息
* @param frameLayout 观众 frameLayout(渲染 View 的 ViewGroup用于计算混流位置)
*/
public void addAudienceMixTranscodingConfig(InteractiveUserData audienceUserData, FrameLayout frameLayout) {
if (audienceUserData == null || TextUtils.isEmpty(audienceUserData.channelId) || TextUtils.isEmpty(audienceUserData.userId)) {
return;
}
if (mAlivcLivePushConfig == null) {
return;
}
if (mAlivcLivePusher == null) {
return;
}
AlivcLiveMixStream audienceMixStream = new AlivcLiveMixStream();
audienceMixStream.userId = audienceUserData.userId;
int size = mMultiInteractLiveMixStreamsArray.size() - 1;
audienceMixStream.x = size % 3 * frameLayout.getWidth() / 3;
audienceMixStream.y = size / 3 * frameLayout.getHeight() / 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 removeLiveMixTranscodingConfig(InteractiveUserData userData) {
if (userData == null || TextUtils.isEmpty(userData.channelId) || TextUtils.isEmpty(userData.userId)) {
return;
}
AlivcLiveMixStream mixStream = findMixStreamByUserData(userData);
if (mixStream == null) {
return;
}
mMultiInteractLiveMixStreamsArray.remove(mixStream);
//多人 PK 混流,结束 PK 后,重新排版混流界面,防止出现覆盖现象
int size = mMultiInteractLiveMixStreamsArray.size() - 1;
for (int i = 1; i < mMultiInteractLiveMixStreamsArray.size(); i++) {
AlivcLiveMixStream alivcLiveMixStream = mMultiInteractLiveMixStreamsArray.get(i);
alivcLiveMixStream.x = ((size - i) % 3) * alivcLiveMixStream.width;
alivcLiveMixStream.y = (size - i) / 3 * alivcLiveMixStream.height;
alivcLiveMixStream.width = alivcLiveMixStream.width;
alivcLiveMixStream.height = alivcLiveMixStream.height;
}
//Array 中只剩主播 id说明无人连麦
if (mMultiInteractLiveMixStreamsArray.size() == 1 && mMultiInteractLiveMixStreamsArray.get(0).userId.equals(userData.userId)) {
clearLiveMixTranscodingConfig();
} else {
mMixInteractLiveTranscodingConfig.mixStreams = mMultiInteractLiveMixStreamsArray;
if (mAlivcLivePusher != null) {
mAlivcLivePusher.setLiveMixTranscodingConfig(mMixInteractLiveTranscodingConfig);
}
}
}
}

View File

@@ -0,0 +1,104 @@
<?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">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_bg_pk" />
<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/pk_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.commonui.widgets.ResizableFrameLayout
android:id="@+id/frame_owner"
android:layout_width="150dp"
android:layout_height="0dp"
android:layout_marginTop="32dp"
app:layout_constraintDimensionRatio="w,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_show_connect" />
<com.alivc.live.interactive_common.widget.RoomAndUserInfoView
android:id="@+id/owner_info_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="@id/frame_owner"
app:layout_constraintTop_toTopOf="@id/frame_owner" />
<ImageView
android:id="@+id/iv_icon_pk"
android:layout_width="66dp"
android:layout_height="23dp"
android:src="@drawable/ic_pk_sign"
app:layout_constraintEnd_toEndOf="@id/frame_owner"
app:layout_constraintStart_toStartOf="@id/frame_owner"
app:layout_constraintTop_toBottomOf="@id/frame_owner" />
<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" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_icon_pk">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />
</androidx.core.widget.NestedScrollView>
<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" />
<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" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,229 @@
<?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">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_bg_pk" />
<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/pk_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.commonui.widgets.ResizableFrameLayout
android:id="@+id/frame_owner"
android:layout_width="0dp"
android:layout_height="332dp"
android:layout_marginTop="42dp"
app:layout_constraintEnd_toStartOf="@id/render_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_show_connect" />
<com.alivc.live.commonui.widgets.ResizableFrameLayout
android:id="@+id/render_container"
android:layout_width="0dp"
android:layout_height="332dp"
app:layout_constraintBottom_toBottomOf="@id/frame_owner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/frame_owner"
app:layout_constraintTop_toTopOf="@id/frame_owner" />
<com.alivc.live.interactive_common.widget.RoomAndUserInfoView
android:id="@+id/owner_info_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:layout_constraintStart_toStartOf="@id/frame_owner"
app:layout_constraintTop_toTopOf="@id/frame_owner" />
<com.alivc.live.interactive_common.widget.InteractivePaneControlView
android:id="@+id/owner_ctrl_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/owner_info_view"
app:layout_constraintTop_toBottomOf="@+id/owner_info_view" />
<com.alivc.live.interactive_common.widget.RoomAndUserInfoView
android:id="@+id/other_info_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:layout_constraintStart_toStartOf="@id/render_container"
app:layout_constraintTop_toTopOf="@id/render_container" />
<com.alivc.live.interactive_common.widget.InteractivePaneControlView
android:id="@+id/other_ctrl_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/other_info_view"
app:layout_constraintTop_toBottomOf="@+id/other_info_view" />
<FrameLayout
android:id="@+id/fl_un_connect"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#23262F"
app:layout_constraintBottom_toBottomOf="@+id/render_container"
app:layout_constraintEnd_toEndOf="@+id/render_container"
app:layout_constraintStart_toStartOf="@+id/render_container"
app:layout_constraintTop_toTopOf="@+id/render_container">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_interact_live_un_connect_bg" />
</FrameLayout>
<com.alivc.live.commonui.messageview.AutoScrollMessagesView
android:id="@+id/sei_receive_view"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:layout_width="66dp"
android:layout_height="23dp"
android:src="@drawable/ic_pk_sign"
app:layout_constraintEnd_toEndOf="@id/frame_owner"
app:layout_constraintStart_toEndOf="@id/frame_owner"
app:layout_constraintTop_toTopOf="@id/frame_owner" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/render_container">
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/btn_show_custom_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/btn_local_record"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/btn_external_audio_stream"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/btn_external_video_stream"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/btn_push_dual_audio"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/btn_push_screen_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/btn_change_resolution_and_fps"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/btn_force_ear_monitor"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView
android:id="@+id/tv_connect"
android:layout_width="94dp"
android:layout_height="30dp"
android:layout_marginTop="28dp"
android:background="@drawable/shape_pysh_btn_bg"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="1"
android:text="@string/pk_start_connect"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/render_container" />
<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.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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_connect" />
<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" />
<com.aliyunsdk.queen.menu.QueenBeautyMenu
android:id="@+id/beauty_beauty_menuPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,71 @@
<?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="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp">
<com.alivc.live.commonui.widgets.ResizableFrameLayout
android:id="@+id/render_container"
android:layout_width="100dp"
android:layout_height="0dp"
android:background="@color/alivc_color_black"
app:layout_constraintDimensionRatio="w,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</com.alivc.live.commonui.widgets.ResizableFrameLayout>
<com.alivc.live.interactive_common.widget.RoomAndUserInfoView
android:id="@+id/view_userinfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
app:layout_constraintStart_toStartOf="@id/render_container"
app:layout_constraintTop_toTopOf="@id/render_container" />
<com.alivc.live.interactive_common.widget.InteractivePaneControlView
android:id="@+id/view_ctrl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/view_userinfo"
app:layout_constraintTop_toBottomOf="@+id/view_userinfo" />
<FrameLayout
android:id="@+id/fl_un_connect"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#23262F"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/render_container"
app:layout_constraintEnd_toEndOf="@+id/render_container"
app:layout_constraintStart_toStartOf="@+id/render_container"
app:layout_constraintTop_toTopOf="@+id/render_container">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_interact_live_un_connect_bg" />
</FrameLayout>
<TextView
android:id="@+id/tv_connect"
android:layout_width="94dp"
android:layout_height="30dp"
android:layout_marginTop="10dp"
android:background="@drawable/shape_pysh_btn_bg"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="1"
android:text="@string/pk_start_connect"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/render_container" />
</androidx.constraintlayout.widget.ConstraintLayout>