集成完直播后提交代码

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,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.alivc.live.commonbiz">
<application>
<activity
android:name="com.alivc.live.commonbiz.backdoor.BackDoorActivity"
android:exported="true"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="backdoor"
android:scheme="livepush" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,216 @@
package com.alivc.live.commonbiz;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @note 该类为读取本地音视频流的实现类,用于外部音视频推流
* 注意为了适配本地不同格式的音视频流需要传入对应的profiles才能正常播放
* 如当前项目中capture0.yuv为720p请注意调节demo配置页中的分辨率为720p
*/
public class LocalStreamReader {
private static final String TAG = "LocalStreamReader";
private boolean videoThreadOn = false;
private boolean audioThreadOn = false;
private int videoWidth;
private int videoHeight;
private int videoStride;
private int videoSize;
private int videoRotation;
private int audioSampleRate;
private int audioChannel;
private int audioBufferSize;
public static final class Builder {
private final LocalStreamReader localStreamReader = new LocalStreamReader();
public Builder setVideoWith(int videoWith) {
localStreamReader.videoWidth = videoWith;
return this;
}
public Builder setVideoHeight(int videoHeight) {
localStreamReader.videoHeight = videoHeight;
return this;
}
public Builder setVideoSize(int videoSize) {
localStreamReader.videoSize = videoSize;
return this;
}
public Builder setVideoStride(int videoStride) {
localStreamReader.videoStride = videoStride;
return this;
}
public Builder setVideoRotation(int videoRotation) {
localStreamReader.videoRotation = videoRotation;
return this;
}
public Builder setAudioSampleRate(int audioSampleRate) {
localStreamReader.audioSampleRate = audioSampleRate;
return this;
}
public Builder setAudioChannel(int audioChannel) {
localStreamReader.audioChannel = audioChannel;
return this;
}
public Builder setAudioBufferSize(int audioBufferSize) {
localStreamReader.audioBufferSize = audioBufferSize;
return this;
}
public LocalStreamReader build() {
return localStreamReader;
}
}
public void readYUVData(File f, ReadYUVFileListener listener) {
new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
private AtomicInteger atoInteger = new AtomicInteger(0);
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("LivePushActivity-readYUV-Thread" + atoInteger.getAndIncrement());
return t;
}
}).execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
videoThreadOn = true;
InputStream myInput;
try {
myInput = new FileInputStream(f);
byte[] buffer = new byte[videoSize];
int length = myInput.read(buffer);
//发数据
while (length > 0 && videoThreadOn) {
long pts = System.currentTimeMillis() * 1000;
listener.onVideoStreamData(buffer, pts, videoWidth, videoHeight, videoStride, videoSize, videoRotation);
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
//发数据
length = myInput.read(buffer);
if (length <= 0) {
myInput.close();
myInput = new FileInputStream(f);
length = myInput.read(buffer);
}
}
myInput.close();
videoThreadOn = false;
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
public void readPCMData(File f, ReadPCMFileListener listener) {
new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
private AtomicInteger atoInteger = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("LivePushActivity-readPCM-Thread" + atoInteger.getAndIncrement());
return t;
}
}).execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
audioThreadOn = true;
int allSended = 0;
int sizePerSecond = audioSampleRate * 2;
InputStream myInput;
long startPts = System.nanoTime() / 1000;
try {
myInput = new FileInputStream(f);
byte[] buffer = new byte[audioBufferSize];
int length = myInput.read(buffer, 0, audioBufferSize);
if (audioSampleRate <= 0) {
Log.e(TAG, "audio sample rate error cause return: " + audioSampleRate);
return;
}
double sleep_time = 1000 / (audioSampleRate / 1000);
while (length > 0 && audioThreadOn) {
long start = System.nanoTime();
long pts = System.currentTimeMillis() * 1000;
listener.onAudioStreamData(buffer, length, pts, audioSampleRate, audioChannel);
allSended += length;
if ((allSended * 1000000L / sizePerSecond - 50000) > (pts - startPts)) {
try {
Thread.sleep(45);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
length = myInput.read(buffer);
if (length < audioBufferSize) {
myInput.close();
myInput = new FileInputStream(f);
length = myInput.read(buffer);
}
long end = System.nanoTime();
try {
long real_sleep_time = (long) (sleep_time - (end - start) / 1000 / 1000);
if (real_sleep_time > 0) {
Thread.sleep(real_sleep_time);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
myInput.close();
audioThreadOn = false;
} catch (IOException e) {
Log.e(TAG, "audio IOException: " + e.getLocalizedMessage());
e.printStackTrace();
}
}
});
}
public void stopYUV() {
videoThreadOn = false;
}
public void stopPcm() {
audioThreadOn = false;
}
public interface ReadYUVFileListener {
void onVideoStreamData(byte[] buffer, long pts, int videoWidth, int videoHeight, int videoStride, int videoSize, int videoRotation);
}
public interface ReadPCMFileListener {
void onAudioStreamData(byte[] buffer, int length, long pts, int audioSampleRate, int audioChannel);
}
}

View File

@@ -0,0 +1,26 @@
package com.alivc.live.commonbiz;
import android.content.Context;
import java.io.File;
public class ResourcesConst {
private ResourcesConst() {
}
/**
* capture0.yuv 文件本地保存路径
*/
public static File localCaptureYUVFilePath(Context context) {
return new File(context.getApplicationContext().getExternalFilesDir(null), "capture0.yuv");
}
/**
* 441.pcm 文件本地保存路径
*/
public static File localCapturePCMFilePath(Context context) {
return new File(context.getFilesDir().getPath() + File.separator + "alivc_resource/441.pcm");
}
}

View File

@@ -0,0 +1,138 @@
package com.alivc.live.commonbiz;
import android.app.DownloadManager;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import com.alivc.live.commonutils.DownloadUtil;
import com.alivc.live.commonutils.HttpUtils;
import com.alivc.live.commonutils.FileUtil;
import java.io.File;
import okhttp3.Response;
/**
* 远端资源下载管理类
*
* @note 用于下载存放在云端的yuv文件该yuv文件在外部音视频推流时使用
*/
public class ResourcesDownload {
private static final String TAG = "ResourcesDownload";
private static final String BASIC_YUV_OSS_URL = "https://alivc-demo-cms.alicdn.com/versionProduct/resources/livePush/capture0.yuv";
private static final String GLOBAL_YUV_OSS_URL = "https://alivc-demo-cms.alicdn.com/versionProduct/resources/livePush/capture0_SG.yuv";
private static final String FINAL_OSS_YUV_URL = BASIC_YUV_OSS_URL;
private static final Handler mHandler = new Handler(Looper.getMainLooper());
private ResourcesDownload() {
}
/**
* 下载yuv文件
*/
public static long downloadCaptureYUV(Context context, DownloadUtil.OnDownloadListener listener) {
DownloadUtil downloadUtil = new DownloadUtil(context.getApplicationContext());
File file = ResourcesConst.localCaptureYUVFilePath(context);
Log.d(TAG, "downloadYUV file : " + file.getAbsolutePath());
downloadUtil.setDownloadListener(new DownloadUtil.OnDownloadListener() {
@Override
public void onDownloadSuccess(long downloadId) {
Log.d(TAG, "onDownloadSuccess : " + downloadId);
verifyFile(downloadUtil, file, listener);
}
@Override
public void onDownloadProgress(long downloadId, double percent) {
Log.i(TAG, "onDownloadProgress : " + downloadId + ", " + percent);
if (listener != null) {
mHandler.post(() -> listener.onDownloadProgress(downloadId, percent));
}
}
@Override
public void onDownloadError(long downloadId, int errorCode, String errorMsg) {
Log.e(TAG, "onDownloadError : " + downloadId + ", " + errorCode + ", " + errorMsg);
if (listener != null) {
mHandler.post(() -> listener.onDownloadError(downloadId, errorCode, errorMsg));
}
FileUtil.safeDeleteFile(file);
}
});
if (file.exists()) {
//文件存在,验证完整性
verifyFile(downloadUtil, file, listener);
return -1;
} else {
return downloadUtil.startDownload(FINAL_OSS_YUV_URL, file);
}
}
/**
* 验证文件完整性
* 通过 Response 中 etag 字段获取 OSS 文件的 MD5
*/
private static void verifyFile(DownloadUtil downloadUtil, File file, DownloadUtil.OnDownloadListener listener) {
HttpUtils.get(FINAL_OSS_YUV_URL, new HttpUtils.OnNetWorkListener() {
@Override
public void onSuccess(Object obj) {
Response response = (Response) obj;
// 1、通过oss url获取etag和本地文件的md5进行比对是否一致
String responseMD5 = "";
try {
responseMD5 = response.header("etag").replace("\"", "");
} catch (Exception e) {
e.printStackTrace();
}
if (!TextUtils.isEmpty(responseMD5)) {
String fileMD5 = FileUtil.getFileMD5(file).trim();
if (responseMD5.equalsIgnoreCase(fileMD5)) {
if (listener != null) {
mHandler.post(() -> listener.onDownloadSuccess(-1));
}
return;
}
}
// 2、有时候etag是个非法值并不代表文件的实际md5那么直接对比文件的大小是否一致
long serverFileSize = -1;
try {
serverFileSize = Long.parseLong(response.header("Content-Length").trim());
} catch (Exception e) {
e.printStackTrace();
}
if (file != null && serverFileSize == file.length()) {
if (listener != null) {
mHandler.post(() -> listener.onDownloadSuccess(-1));
}
return;
}
FileUtil.safeDeleteFile(file);
downloadUtil.startDownload(FINAL_OSS_YUV_URL, file);
}
@Override
public void onFailure(int code, String msg) {
if (listener != null) {
mHandler.post(() -> listener.onDownloadError(-1, DownloadManager.ERROR_HTTP_DATA_ERROR, "code=" + code + ",msg=" + msg));
}
}
@Override
public void onError() {
if (listener != null) {
mHandler.post(() -> listener.onDownloadError(-1, DownloadManager.ERROR_HTTP_DATA_ERROR, "NetWork error"));
}
}
});
}
}

View File

@@ -0,0 +1,148 @@
package com.alivc.live.commonbiz;
import android.content.Context;
import androidx.annotation.NonNull;
import com.alivc.live.commonutils.SharedPrefUtils;
/**
* 推流Demo业务SharedPreference
*/
public class SharedPreferenceUtils {
private static final String AUTO_FOCUS = "autofocus";
private static final String PREVIEW_MIRROR = "previewmirror";
private static final String PUSH_MIRROR = "pushmirror";
private static final String TARGET_BIT = "target_bit";
private static final String MIN_BIT = "min_bit";
private static final String SHOW_GUIDE = "guide";
private static final String BEAUTY_ON = "beautyon";
private static final String HINT_TARGET_BIT = "hint_target_bit";
private static final String HINT_MIN_BIT = "hint_min_bit";
private static final String DISPLAY_FIT = "display_fit";
private static final String APP_ID = "app_id";
private static final String APP_KEY = "app_key";
private static final String PLAY_DOMAIN = "play_domain";
private static final String BARE_STREAM = "bare_stream";
private static final String USE_MULTI_PK_16IN = "useMultiPK16IN";
private static final String FORCE_RTC_PRE_ENVIRONMENT = "force_rtc_pre_environment";
public static void setPreviewMirror(@NonNull Context context, boolean previewMirror) {
SharedPrefUtils.saveData(context, PREVIEW_MIRROR, previewMirror);
}
public static boolean isPreviewMirror(@NonNull Context context, boolean defaultValue) {
return SharedPrefUtils.getBooleanData(context, PREVIEW_MIRROR, defaultValue);
}
public static void setPushMirror(@NonNull Context context, boolean pushMirror) {
SharedPrefUtils.saveData(context, PUSH_MIRROR, pushMirror);
}
public static boolean isPushMirror(@NonNull Context context, boolean defaultValue) {
return SharedPrefUtils.getBooleanData(context, PUSH_MIRROR, defaultValue);
}
public static void setAutofocus(@NonNull Context context, boolean autofocus) {
SharedPrefUtils.saveData(context, AUTO_FOCUS, autofocus);
}
public static boolean isAutoFocus(@NonNull Context context, boolean defaultValue) {
return SharedPrefUtils.getBooleanData(context, AUTO_FOCUS, defaultValue);
}
public static void setTargetBit(@NonNull Context context, int target) {
SharedPrefUtils.saveData(context, TARGET_BIT, target);
}
public static int getTargetBit(@NonNull Context context) {
return SharedPrefUtils.getIntData(context, TARGET_BIT, 0);
}
public static void setMinBit(@NonNull Context context, int min) {
SharedPrefUtils.saveData(context, MIN_BIT, min);
}
public static int getMinBit(@NonNull Context context) {
return SharedPrefUtils.getIntData(context, MIN_BIT, 0);
}
public static void setHintTargetBit(@NonNull Context context, int hintTarget) {
SharedPrefUtils.saveData(context, HINT_TARGET_BIT, hintTarget);
}
public static int getHintTargetBit(@NonNull Context context) {
return SharedPrefUtils.getIntData(context, HINT_TARGET_BIT, 0);
}
public static void setHintMinBit(@NonNull Context context, int hintMin) {
SharedPrefUtils.saveData(context, HINT_MIN_BIT, hintMin);
}
public static int getHintMinBit(@NonNull Context context) {
return SharedPrefUtils.getIntData(context, HINT_MIN_BIT, 0);
}
public static void setGuide(@NonNull Context context, boolean guide) {
SharedPrefUtils.saveData(context, SHOW_GUIDE, guide);
}
public static boolean isGuide(@NonNull Context context) {
return SharedPrefUtils.getBooleanData(context, SHOW_GUIDE, true);
}
public static void setBeautyOn(@NonNull Context context, boolean beautyOn) {
SharedPrefUtils.saveData(context, BEAUTY_ON, beautyOn);
}
public static boolean isBeautyOn(@NonNull Context context) {
return SharedPrefUtils.getBooleanData(context, BEAUTY_ON, true);
}
public static void setDisplayFit(@NonNull Context context, int displayfit) {
SharedPrefUtils.saveData(context, DISPLAY_FIT, displayfit);
}
public static int getDisplayFit(@NonNull Context context, int defaultValue) {
return SharedPrefUtils.getIntData(context, DISPLAY_FIT, defaultValue);
}
public static void setAppInfo(@NonNull Context context, String appId, String appKey, String playDomain) {
SharedPrefUtils.saveData(context, APP_ID, appId);
SharedPrefUtils.saveData(context, APP_KEY, appKey);
SharedPrefUtils.saveData(context, PLAY_DOMAIN, playDomain);
}
public static String getAppId(@NonNull Context context) {
return SharedPrefUtils.getStringData(context, APP_ID, "");
}
public static String getAppKey(@NonNull Context context) {
return SharedPrefUtils.getStringData(context, APP_KEY, "");
}
public static String getPlayDomain(@NonNull Context context) {
return SharedPrefUtils.getStringData(context, PLAY_DOMAIN, "");
}
public static void setMultiPK16IN(@NonNull Context context, boolean value) {
SharedPrefUtils.saveData(context, USE_MULTI_PK_16IN, value);
}
public static boolean getMultiPK16IN(@NonNull Context context) {
return SharedPrefUtils.getBooleanData(context, USE_MULTI_PK_16IN, false);
}
public static void setForceRTCPreEnvironment(@NonNull Context context, boolean value) {
SharedPrefUtils.saveData(context, FORCE_RTC_PRE_ENVIRONMENT, value);
}
public static boolean getForceRTCPreEnvironment(@NonNull Context context) {
return SharedPrefUtils.getBooleanData(context, FORCE_RTC_PRE_ENVIRONMENT, false);
}
public static void clear(@NonNull Context context) {
SharedPrefUtils.clear(context);
}
}

View File

@@ -0,0 +1,187 @@
package com.alivc.live.commonbiz.backdoor;
import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.alivc.live.commonbiz.R;
import com.alivc.live.commonbiz.SharedPreferenceUtils;
import com.alivc.live.commonui.utils.StatusBarUtil;
import com.alivc.live.commonui.widgets.LivePushTextSwitch;
import com.alivc.live.commonutils.AppUtil;
import com.alivc.live.commonutils.FileUtil;
import com.alivc.live.commonutils.ToastUtils;
import com.alivc.live.pusher.AlivcLiveBase;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
/**
* @author keria
* @date 2023/10/13
* @brief
*/
public class BackDoorActivity extends AppCompatActivity {
private static final String SD_CARD_LOG_SUFFIX = "device-info.log";
private TextView mInfoTv;
private LivePushTextSwitch mForceRTCPreEnvSw;
private LivePushTextSwitch mMultiPK16InSw;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StatusBarUtil.translucent(this, Color.TRANSPARENT);
setContentView(R.layout.push_activity_backdoor);
initViews();
initData();
}
private void initViews() {
mForceRTCPreEnvSw = findViewById(R.id.sw_force_rtc_pre_env);
mForceRTCPreEnvSw.setTextViewText(getString(R.string.backdoor_rtc_force_pre_env));
mForceRTCPreEnvSw.setOnSwitchToggleListener(isChecked -> {
if (BackDoorInstance.getInstance().isForceRTCPreEnvironment() != isChecked) {
BackDoorInstance.getInstance().setForceRTCPreEnvironment(isChecked);
// 清除当前连麦配置信息
SharedPreferenceUtils.setAppInfo(getApplicationContext(), "", "", "");
// 退出APP
killApp();
}
});
mMultiPK16InSw = findViewById(R.id.sw_multi_pk_16in);
mMultiPK16InSw.setTextViewText(getString(R.string.backdoor_multi_pk_16in));
mMultiPK16InSw.setOnSwitchToggleListener(isChecked -> {
if (BackDoorInstance.getInstance().isUseMultiPK16IN() != isChecked) {
BackDoorInstance.getInstance().setUseMultiPK16IN(isChecked);
}
});
mInfoTv = findViewById(R.id.info_tv);
StringBuffer sb = new StringBuffer();
sb.append(getDemoBuildInfo());
sb.append("\n\n");
sb.append(getSdkBuildInfo());
sb.append("\n\n");
sb.append(AppUtil.getBriefDeviceInfo());
sb.append("\n\n");
sb.append(AppUtil.getDeviceInfo());
sb.append("\n\n");
sb.append(AppUtil.getFullCPUInfo());
sb.append("\n\n");
sb.append(AppUtil.getOpenGLESInfo(this));
sb.append("\n\n");
mInfoTv.setText(sb.toString());
mInfoTv.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
copyInfo2Clipboard();
dumpInfo2SdCard();
return true;
}
});
}
private void initData() {
mForceRTCPreEnvSw.setSwitchChecked(BackDoorInstance.getInstance().isForceRTCPreEnvironment());
mMultiPK16InSw.setSwitchChecked(BackDoorInstance.getInstance().isUseMultiPK16IN());
}
private static String getDemoBuildInfo() {
return String.format("%s%n%s%n%s%n%s",
"Demo Build Type: " + com.alivc.live.commonbiz.BuildConfig.BUILD_TYPE,
"Demo Build AIO: " + com.alivc.live.commonbiz.BuildConfig.MTL_BUILD_FOR_AIO,
"Demo Build ID: " + com.alivc.live.commonbiz.BuildConfig.MTL_BUILD_ID,
"Demo Build Timestamp: " + com.alivc.live.commonbiz.BuildConfig.MTL_BUILD_TIMESTAMP
);
}
private static String getSdkBuildInfo() {
return String.format("%s%n%s%n%s%n%s%n%s%n%s",
"SDK Version: " + AlivcLiveBase.getSDKVersion(),
"SDK Build Type: " + com.alivc.live.pusher.BuildConfig.BUILD_TYPE,
"SDK Build Interactive: " + com.alivc.live.pusher.BuildConfig.BUILD_INTERACTIVE,
"SDK Pre Environment: " + com.alivc.live.pusher.BuildConfig.PUSH_SDK_PRE_ENV,
"SDK Head Commit ID: " + com.alivc.live.pusher.BuildConfig.HEAD_COMMIT_ID,
"SDK Build ID: " + com.alivc.live.pusher.BuildConfig.MTL_BUILD_ID,
"SDK Build Timestamp: " + com.alivc.live.pusher.BuildConfig.MTL_BUILD_TIMESTAMP
);
}
private void copyInfo2Clipboard() {
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if (cm == null) {
return;
}
cm.setText(mInfoTv.getText());
ToastUtils.show("copy to clipboard success!");
}
private void dumpInfo2SdCard() {
removeOldSdCardLogs();
String folderPath = getSdCardLogPath();
String filename = String.format("%s-%s", new SimpleDateFormat("yyMMddHHmmss").format(new Date()), SD_CARD_LOG_SUFFIX);
String finalPath = FileUtil.combinePaths(folderPath, filename);
FileWriter fr = null;
try {
fr = new FileWriter(finalPath);
fr.write(mInfoTv.getText().toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void removeOldSdCardLogs() {
String folderPath = getSdCardLogPath();
ArrayList<File> files = FileUtil.listAllFiles(new File(folderPath), false);
if (files != null && !files.isEmpty()) {
for (File file : files) {
String path = file.getAbsolutePath();
if (path.endsWith(SD_CARD_LOG_SUFFIX)) {
FileUtil.safeDeleteFile(path);
}
}
}
}
private String getSdCardLogPath() {
String parentPath = getExternalFilesDir("").getAbsolutePath();
String folderPath = FileUtil.combinePaths(parentPath, "Ali_RTS_Log");
FileUtil.safeCreateFolder(folderPath);
return folderPath;
}
/**
* 强制退出APP
*/
private static void killApp() {
try {
Thread.sleep(2 * 1000L);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,47 @@
package com.alivc.live.commonbiz.backdoor;
import com.alivc.live.commonbiz.SharedPreferenceUtils;
import com.alivc.live.commonutils.ContextUtils;
/**
* @author keria
* @date 2023/10/13
* @brief
*/
public class BackDoorInstance {
private boolean forceRTCPreEnvironment;
private boolean useMultiPK16IN;
private BackDoorInstance() {
forceRTCPreEnvironment = SharedPreferenceUtils.getForceRTCPreEnvironment(ContextUtils.getContext());
useMultiPK16IN = SharedPreferenceUtils.getMultiPK16IN(ContextUtils.getContext());
}
public static BackDoorInstance getInstance() {
return Inner.instance;
}
public boolean isForceRTCPreEnvironment() {
return this.forceRTCPreEnvironment;
}
public void setForceRTCPreEnvironment(boolean forceRTCPreEnvironment) {
this.forceRTCPreEnvironment = forceRTCPreEnvironment;
SharedPreferenceUtils.setForceRTCPreEnvironment(ContextUtils.getContext(), forceRTCPreEnvironment);
}
public boolean isUseMultiPK16IN() {
return this.useMultiPK16IN;
}
public void setUseMultiPK16IN(boolean useMultiPK16IN) {
this.useMultiPK16IN = useMultiPK16IN;
SharedPreferenceUtils.setMultiPK16IN(ContextUtils.getContext(), useMultiPK16IN);
}
private static class Inner {
private static final BackDoorInstance instance = new BackDoorInstance();
}
}

View File

@@ -0,0 +1,55 @@
package com.alivc.live.commonbiz.seidelay;
import com.alivc.live.commonbiz.seidelay.api.ISEIDelayEventListener;
import com.alivc.live.commonbiz.seidelay.handle.SEIDelayProvider;
import com.alivc.live.commonbiz.seidelay.handle.SEIDelayReceiver;
import com.alivc.live.commonbiz.seidelay.time.SEIDelayTimeHandler;
/**
* @author keria
* @date 2023/12/5
* @brief
*/
public class SEIDelayManager {
private SEIDelayProvider mProvider;
private SEIDelayReceiver mReceiver;
public SEIDelayManager() {
requestNTPTime();
}
public void registerProvider(String src, ISEIDelayEventListener listener) {
mProvider = new SEIDelayProvider();
mProvider.addListener(src, listener);
}
public void registerReceiver(ISEIDelayEventListener listener) {
mReceiver = new SEIDelayReceiver();
mReceiver.addListener("receiver", listener);
}
public void receiveSEI(SEISourceType sourceType, String sei) {
if (mReceiver != null) {
mReceiver.receiveSEI(sourceType, sei);
}
}
private void requestNTPTime() {
try {
new Thread(SEIDelayTimeHandler::requestNTPTime).start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void destroy() {
if (mProvider != null) {
mProvider.destroy();
mProvider = null;
}
if (mReceiver != null) {
mReceiver.destroy();
mReceiver = null;
}
}
}

View File

@@ -0,0 +1,11 @@
package com.alivc.live.commonbiz.seidelay;
/**
* @author keria
* @date 2023/12/5
* @brief
*/
public enum SEISourceType {
CDN,
RTC
}

View File

@@ -0,0 +1,17 @@
package com.alivc.live.commonbiz.seidelay.api;
/**
* @author keria
* @date 2023/12/5
* @brief
*/
public interface ISEIDelayEventListener {
/**
* SEI delay event callback
*
* @param src SEI delay event source
* @param type SEI delay event type
* @param msg SEI delay event message
*/
void onEvent(String src, String type, String msg);
}

View File

@@ -0,0 +1,25 @@
package com.alivc.live.commonbiz.seidelay.api;
import java.util.HashMap;
import java.util.Map;
/**
* @author keria
* @date 2023/12/5
* @brief
*/
public abstract class ISEIDelayHandle {
public final Map<String, ISEIDelayEventListener> listenerHashMap = new HashMap<>();
public void addListener(String src, ISEIDelayEventListener listener) {
listenerHashMap.put(src, listener);
}
public void removeListener(String src) {
listenerHashMap.remove(src);
}
public void destroy() {
listenerHashMap.clear();
}
}

View File

@@ -0,0 +1,62 @@
package com.alivc.live.commonbiz.seidelay.data;
import org.json.JSONException;
import org.json.JSONObject;
/**
* @author keria
* @date 2023/12/5
* @brief
*/
public class SEIDelayProtocol {
private static final String SEI_DELAY = "sei_delay";
private static final String TIMESTAMP = "tm";
private static final String SRC = "src";
public String src = null;
public long tm = -1L;
public SEIDelayProtocol(String src, long tm) {
this.src = src;
this.tm = tm;
}
public SEIDelayProtocol(String json) {
try {
JSONObject jsonObject = new JSONObject(json);
String seiDelay = jsonObject.getString(SEI_DELAY);
JSONObject jsonObject2 = new JSONObject(seiDelay);
this.src = jsonObject2.getString(SRC);
this.tm = jsonObject2.getLong(TIMESTAMP);
} catch (JSONException e) {
}
}
private String generate() {
JSONObject jsonObject1 = new JSONObject();
try {
jsonObject1.put(SRC, this.src);
jsonObject1.put(TIMESTAMP, this.tm);
} catch (JSONException e) {
e.printStackTrace();
}
JSONObject jsonObject2 = new JSONObject();
try {
jsonObject2.put(SEI_DELAY, jsonObject1.toString());
} catch (JSONException e) {
e.printStackTrace();
}
return jsonObject2.toString();
}
@Override
public String toString() {
return generate();
}
}

View File

@@ -0,0 +1,88 @@
package com.alivc.live.commonbiz.seidelay.handle;
import com.alivc.live.commonbiz.seidelay.api.ISEIDelayEventListener;
import com.alivc.live.commonbiz.seidelay.api.ISEIDelayHandle;
import com.alivc.live.commonbiz.seidelay.data.SEIDelayProtocol;
import com.alivc.live.commonbiz.seidelay.time.SEIDelayTimeHandler;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Handles SEIDelay tasks and events.
* 对象 SEIDelayProvider 是一个处理 SEIDelay 任务和事件的类。
* 它使用一个计划好的任务执行器来定期运行任务。
* 当有监听器被添加或移除时,它会相应地启动或停止任务。
* 这个类主要处理 SEI 延迟相关的逻辑。
*
* @author keria
* @date 2023/12/5
* @brief
*/
public class SEIDelayProvider extends ISEIDelayHandle {
private static final long SCHEDULED_EXECUTOR_SERVICE_PERIOD = 2 * 1000L;
private static final int CORE_POOL_SIZE = 1;
private ScheduledThreadPoolExecutor mScheduledExecutorService;
private volatile boolean mTaskRun = false;
@Override
public void addListener(String src, ISEIDelayEventListener listener) {
super.addListener(src, listener);
if (!mTaskRun && !listenerHashMap.isEmpty()) {
mTaskRun = true;
startTask();
}
}
@Override
public void removeListener(String src) {
super.removeListener(src);
if (mTaskRun && listenerHashMap.isEmpty()) {
mTaskRun = false;
stopTask();
}
}
@Override
public void destroy() {
super.destroy();
stopTask();
}
private void startTask() {
stopTask();
mScheduledExecutorService = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(CORE_POOL_SIZE);
mScheduledExecutorService.scheduleAtFixedRate(this::provideSEI, 0, SCHEDULED_EXECUTOR_SERVICE_PERIOD, TimeUnit.MILLISECONDS);
}
private void stopTask() {
if (mScheduledExecutorService != null) {
mScheduledExecutorService.shutdown();
try {
if (!mScheduledExecutorService.awaitTermination(1, TimeUnit.SECONDS)) {
mScheduledExecutorService.shutdownNow();
}
} catch (InterruptedException e) {
mScheduledExecutorService.shutdownNow();
Thread.currentThread().interrupt();
e.printStackTrace();
} finally {
mScheduledExecutorService = null;
}
}
}
private void provideSEI() {
for (Map.Entry<String, ISEIDelayEventListener> entry : listenerHashMap.entrySet()) {
ISEIDelayEventListener eventListener = entry.getValue();
if (eventListener != null && SEIDelayTimeHandler.isNtpTimeUpdated()) {
long ntpTimestamp = SEIDelayTimeHandler.getCurrentTimestamp();
SEIDelayProtocol dataProtocol = new SEIDelayProtocol(entry.getKey(), ntpTimestamp);
eventListener.onEvent(dataProtocol.src, dataProtocol.src, dataProtocol.toString());
}
}
}
}

View File

@@ -0,0 +1,68 @@
package com.alivc.live.commonbiz.seidelay.handle;
import com.alivc.live.commonbiz.seidelay.SEISourceType;
import com.alivc.live.commonbiz.seidelay.api.ISEIDelayEventListener;
import com.alivc.live.commonbiz.seidelay.api.ISEIDelayHandle;
import com.alivc.live.commonbiz.seidelay.data.SEIDelayProtocol;
import com.alivc.live.commonbiz.seidelay.time.SEIDelayTimeHandler;
import java.util.Map;
/**
* SEIDelayReceiver handles the reception and processing of SEI data.
* It notifies listeners with the delay information.
*
* @author keria
* @date 2023/12/5
* @brief
*/
public class SEIDelayReceiver extends ISEIDelayHandle {
private static final long INVALID_THRESH_HOLD = 1000 * 60L;
@Override
public void destroy() {
super.destroy();
}
/**
* Receives SEI data and processes it to calculate delay and notify listeners.
*
* @param sourceType The type of SEI source.
* @param sei The SEI data.
*/
public void receiveSEI(SEISourceType sourceType, String sei) {
if (listenerHashMap.isEmpty()) {
return;
}
if (!SEIDelayTimeHandler.isNtpTimeUpdated()) {
return;
}
SEIDelayProtocol dataProtocol = new SEIDelayProtocol(sei);
long ntpTimestamp = SEIDelayTimeHandler.getCurrentTimestamp();
long delay = ntpTimestamp - dataProtocol.tm;
if (delay > INVALID_THRESH_HOLD) {
return;
}
notifyListeners(dataProtocol, sourceType, delay);
}
/**
* Notifies all registered listeners with the delay information.
*
* @param dataProtocol The SEIDelayProtocol containing SEI data.
* @param sourceType The type of SEI source.
* @param delay The calculated delay.
*/
private void notifyListeners(SEIDelayProtocol dataProtocol, SEISourceType sourceType, long delay) {
for (Map.Entry<String, ISEIDelayEventListener> entry : listenerHashMap.entrySet()) {
ISEIDelayEventListener eventListener = entry.getValue();
if (eventListener != null) {
eventListener.onEvent(dataProtocol.src, sourceType.toString(), String.valueOf(delay));
}
}
}
}

View File

@@ -0,0 +1,58 @@
package com.alivc.live.commonbiz.seidelay.time;
/**
* @author keria
* @date 2023/12/5
* @brief
*/
import android.util.Log;
import org.apache.commons.net.ntp.NTPUDPClient;
import org.apache.commons.net.ntp.TimeInfo;
import java.net.InetAddress;
public class SEIDelayTimeHandler {
private static final String TAG = "SEIDelayTimeHandler";
private static final String NTP_HOST = "ntp.aliyun.com";
private static long NTP_DELAY = 0L;
private static boolean NTP_TIME_UPDATED = false;
private SEIDelayTimeHandler() {
}
public static void requestNTPTime() {
NTPUDPClient client = new NTPUDPClient();
client.setDefaultTimeout(5000);
try {
InetAddress ntpServerAddress = InetAddress.getByName(NTP_HOST);
Log.i(TAG, "Connecting to NTP server: " + NTP_HOST);
TimeInfo timeInfo = client.getTime(ntpServerAddress);
timeInfo.computeDetails();
long returnTime = timeInfo.getMessage().getTransmitTimeStamp().getTime();
long systemTime = System.currentTimeMillis();
NTP_DELAY = returnTime - systemTime;
NTP_TIME_UPDATED = true;
Log.i(TAG, "Current time (from " + NTP_HOST + "): " + returnTime);
Log.i(TAG, "Current time (from system): " + systemTime);
Log.i(TAG, "Delay (NTP-system): " + NTP_DELAY + "ms");
} catch (Exception e) {
e.printStackTrace();
} finally {
client.close();
}
}
public static boolean isNtpTimeUpdated() {
return NTP_TIME_UPDATED;
}
public static long getCurrentTimestamp() {
return System.currentTimeMillis() + NTP_DELAY;
}
}

View File

@@ -0,0 +1,205 @@
package com.alivc.live.commonbiz.test;
import android.text.TextUtils;
import java.util.HashMap;
public class AliLiveStreamURLUtil {
/**
* 连麦推拉流地址的APPID/APPKEY/PLAY_DOMAIN
*/
public static final String APP_ID = PushDemoTestConstants.getTestInteractiveAppID();
public static final String APP_KEY = PushDemoTestConstants.getTestInteractiveAppKey();
public static final String PLAY_DOMAIN = PushDemoTestConstants.getTestInteractivePlayDomain();
/**
* 连麦推拉流地址的RTMP/HTTP-FLV/ARTC协议头
*/
private static final String RTMP = "rtmp://";
private static final String HTTP = "http://";
private static final String FLV = ".flv";
private static final String ARTC = "artc://";
/**
* DOMAIN 固定字段。
*/
public static final String ALILIVE_INTERACTIVE_DOMAIN = "live.aliyun.com";
/**
* 连麦推拉流地址的二级域名Second-level domain
*/
private static final String PULL_SLD = "play";
private static final String PUSH_SLD = "push";
/**
* 连麦推拉流地址的参数配置key
*/
public static final String BASIC_DOMAIN = "basicDomain";
public static final String SDK_APP_ID = "sdkAppId";
public static final String USER_ID = "userId";
public static final String TIMESTAMP = "timestamp";
public static final String TOKEN = "token";
private static final String APP_NAME = "live";
/**
* 旁路混流地址用,纯音频--->audio音视频--->camera
*/
private static final String STREAM_TASK_TYPE_CAMERA = "camera";
private static final String STREAM_TASK_TYPE_AUDIO = "audio";
private AliLiveStreamURLUtil() {
}
/**
* 根据 APPID/APPKEY/PLAY_DOMAIN 连麦配置信息,生成连麦推流地址
* <p>
* 需提前配置好以下连麦配置:
* {@link AliLiveUserSigGenerate#ALILIVE_APPID}
* {@link AliLiveUserSigGenerate#ALILIVE_APPKEY}
* {@link AliLiveUserSigGenerate#ALILIVE_PLAY_DOMAIN}
*
* @param channelId 频道 ID
* @param userId 用户 ID
* @return 连麦推流地址
*/
public static String generateInteractivePushUrl(String channelId, String userId) {
return generateInteractiveUrl(channelId, userId, true);
}
/**
* 根据 APPID/APPKEY/PLAY_DOMAIN 连麦配置信息,生成连麦拉流地址
* <p>
* 需提前配置好以下连麦配置:
* {@link AliLiveUserSigGenerate#ALILIVE_APPID}
* {@link AliLiveUserSigGenerate#ALILIVE_APPKEY}
* {@link AliLiveUserSigGenerate#ALILIVE_PLAY_DOMAIN}
*
* @param channelId 频道 ID
* @param userId 用户 ID
* @return 连麦实时拉流地址RTC用户实时互通用
*/
public static String generateInteractivePullUrl(String channelId, String userId) {
return generateInteractiveUrl(channelId, userId, false);
}
private static String generateInteractiveUrl(String channelId, String userId, boolean isPush) {
String sld = isPush ? PUSH_SLD : PULL_SLD;
long timestamp = AliLiveUserSigGenerate.getTimesTamp();
String token = AliLiveUserSigGenerate.createToken(APP_ID, APP_KEY, channelId, userId, timestamp);
return new StringBuilder(ARTC)
.append(ALILIVE_INTERACTIVE_DOMAIN).append("/")
.append(sld).append("/")
.append(channelId)
.append("?")
.append(SDK_APP_ID).append("=").append(APP_ID).append("&")
.append(USER_ID).append("=").append(userId).append("&")
.append(TIMESTAMP).append("=").append(timestamp).append("&")
.append(TOKEN).append("=").append(token)
.toString();
}
/**
* 根据 APPID/APPKEY/PLAY_DOMAIN 连麦配置信息生成普通观众的CDN拉流地址
* <p>
* 需提前配置好以下连麦配置:
* {@link AliLiveUserSigGenerate#ALILIVE_APPID}
* {@link AliLiveUserSigGenerate#ALILIVE_APPKEY}
* {@link AliLiveUserSigGenerate#ALILIVE_PLAY_DOMAIN}
*
* @param channelId 频道 ID
* @param userId 用户 ID
* @return 旁路直播拉流地址,普通观众用
*/
public static String generateCDNPullUrl(String channelId, String userId, boolean isAudioOnly) {
/**
* 建议将rtmp换成http-flv的形式。理由如下
* 在阿里云点播控制台生成地址时会同时生成RTMP与HTTP-FLV的地址这两个协议里包含的数据内容是一致的只是网络协议通道不一样。
* <p>
* HTTP协议是互联网主要协议CDN、运营商、中间网络设备等链路中都对HTTP有很长时间的网络优化
* HTTP的默认80/443端口号也是常见白名单端口不容易被禁用而RTMP协议比较老其默认端口号是1935有可能被防火墙等设备禁用导致异常。
* 因此在综合网络环境下HTTP-FLV的稳定性、性能卡顿、延时会比RTMP更好。
*/
return generateCDNFlvPullUrl(channelId, userId, isAudioOnly);
}
/**
* Parse url into auth info
*
* @param url url
* @return url params
*/
public static HashMap<String, String> parseUrl(String url) {
HashMap<String, String> map = new HashMap<>();
if (TextUtils.isEmpty(url)) {
return map;
}
String[] urlParts = url.trim().split("\\?");
map.put(BASIC_DOMAIN, urlParts[0]);
if (urlParts.length == 1) {
return map;
}
String[] params = urlParts[1].split("&");
for (String param : params) {
String[] keyValue = param.split("=");
if (keyValue.length < 2) {
continue;
}
map.put(keyValue[0], keyValue[1]);
}
return map;
}
/**
* Parse stream name from url params
*
* @param params url params
* @return stream name
*/
public static String parseURLStreamName(HashMap<String, String> params) {
if (params == null || !params.containsKey(BASIC_DOMAIN)) {
return "";
}
String domain = params.get(BASIC_DOMAIN);
if (TextUtils.isEmpty(domain)) {
return "";
}
String[] parts = domain.split("/");
return parts[parts.length - 1];
}
private static String generateCDNFlvPullUrl(String channelId, String userId, boolean isAudioOnly) {
String streamTaskType = isAudioOnly ? STREAM_TASK_TYPE_AUDIO : STREAM_TASK_TYPE_CAMERA;
return new StringBuilder(HTTP)
.append(PLAY_DOMAIN).append('/')
.append(APP_NAME).append('/')
.append(APP_ID).append('_')
.append(channelId).append('_')
.append(userId).append('_')
.append(streamTaskType)
.append(FLV)
.toString();
}
private static String generateCDNRtmpPullUrl(String channelId, String userId, boolean isAudioOnly) {
String streamTaskType = isAudioOnly ? STREAM_TASK_TYPE_AUDIO : STREAM_TASK_TYPE_CAMERA;
return new StringBuilder(RTMP)
.append(PLAY_DOMAIN).append('/')
.append(APP_NAME).append('/')
.append(APP_ID).append('_')
.append(channelId).append('_')
.append(userId).append('_')
.append(streamTaskType)
.toString();
}
}

View File

@@ -0,0 +1,85 @@
package com.alivc.live.commonbiz.test;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @note 备注该文件被直播连麦控制台的demo试用所链接请谨慎改动此文件的目录位置。
*/
public class AliLiveUserSigGenerate {
/**
* APP_ID在阿里云控制台应用管理页面创建和查看。
*/
public static final String ALILIVE_APPID = "PLACEHOLDER";
/**
* APP_KEY在阿里云控制台应用管理页面创建和查看。
*/
public static final String ALILIVE_APPKEY = "PLACEHOLDER";
/**
* CDN_DOMAIN在阿里云控制台应用管理页面创建和查看。
*/
public static final String ALILIVE_PLAY_DOMAIN = "PLACEHOLDER";
/**
* 过期时间
* 时间单位秒代表令牌有效时间。可设置最大范围是小于等于1天建议不要设置的过短或超过1天超过1天会不安全。
* 默认时间1天。1天 = 60 x 60 x 24。
*/
public static long getTimesTamp() {
return System.currentTimeMillis() / 1000 + 60 * 60 * 24;
}
/**
* 根据 appidappkeychannelIduserIdnonctimestamp 生层 token
*
* @param appid 应用ID。在控制台应用管理页面创建和查看。
* @param appkey 在控制台应用管理页面创建和查看。
* @param channelId 频道 ID
* @param userId 用户 ID
* @param timestamp 过期时间戳
* @return token
*/
public static String createToken(String appid, String appkey, String channelId, String userId, long timestamp) {
StringBuilder stringBuilder = new StringBuilder()
.append(appid)
.append(appkey)
.append(channelId)
.append(userId)
.append(timestamp);
return getSHA256(stringBuilder.toString());
}
/**
* 字符串签名
*
* @param str 输入源
* @return 返回签名
*/
public static String getSHA256(String str) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));
return byte2Hex(hash);
} catch (NoSuchAlgorithmException e) {
// Consider logging the exception and/or re-throwing as a RuntimeException
e.printStackTrace();
}
return "";
}
private static String byte2Hex(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
// Use single quote for char
stringBuilder.append('0');
}
stringBuilder.append(hex);
}
return stringBuilder.toString();
}
}

View File

@@ -0,0 +1,161 @@
package com.alivc.live.commonbiz.test;
import android.content.Context;
import android.text.TextUtils;
import com.alivc.live.commonbiz.BuildConfig;
import com.alivc.live.commonbiz.R;
import com.alivc.live.commonbiz.SharedPreferenceUtils;
import com.alivc.live.commonutils.ContextUtils;
/**
* Created by baorunchen on 2022/8/31.
* <p>
* Develop test data class. ONLY FOR TEST!!!!
* <p>
* First, we store dev test data into `local.properties` file, it's a gitignore file.
* <p>
* Such as:
* sdk.dir=/Users/keria/Library/Android/sdk
* push.url=rtmp://push-demo-rtmp.aliyunlive.com/test/stream/keriatest?&auth_key=xxxx
* pull.url=
* <p>
* And then, we can parse it from `build.gradle` file,
* if `local.properties` hasn't the property, it will use the default value.
*/
public class PushDemoTestConstants {
private static final String PLACEHOLDER = "PLACEHOLDER";
/**
* Get test push url, you won't need to scan url again!!!
* <p>
* put your test push url into `local.properties`
* such as: push.url=rtmp://xxx
*
* @return test push url
*/
public static String getTestPushUrl() {
Context context = ContextUtils.getContext();
return (context != null) ? context.getString(R.string.test_push_url) : "";
}
/**
* Get test pull url, you won't need to scan url again!!!
* <p>
* put your test pull url into `local.properties`
* such as: pull.url=rtmp://xxx
*
* @return test push url
*/
public static String getTestPullUrl() {
Context context = ContextUtils.getContext();
return (context != null) ? context.getString(R.string.test_pull_url) : "";
}
/**
* Get test interactive app id
* <p>
* put your app id into `local.properties`
* such as: interactive.appid=keriatest-appid
* <p>
* or, you can export it into system environment
* such as: export INTERACTIVE_APP_ID=keriatest-appid
*
* @return interactive app id
*/
public static String getTestInteractiveAppID() {
if (!checkIsPlaceholder(AliLiveUserSigGenerate.ALILIVE_APPID)) {
return AliLiveUserSigGenerate.ALILIVE_APPID;
}
if(!TextUtils.isEmpty(SharedPreferenceUtils.getAppId(ContextUtils.getApplicationContext()))){
return SharedPreferenceUtils.getAppId(ContextUtils.getApplicationContext());
}
if (!TextUtils.isEmpty(BuildConfig.INTERACTIVE_APP_ID)) {
return BuildConfig.INTERACTIVE_APP_ID;
}
Context context = ContextUtils.getContext();
if (context != null) {
return context.getString(R.string.interactive_appid);
}
return AliLiveUserSigGenerate.ALILIVE_APPID;
}
/**
* Get test interactive app key
* <p>
* put your app key into `local.properties`
* such as: interactive.appkey=keriatest-appkey
* <p>
* or, you can export it into system environment
* such as: export INTERACTIVE_APP_KEY=keriatest-appkey
*
* @return interactive app key
*/
public static String getTestInteractiveAppKey() {
if (!checkIsPlaceholder(AliLiveUserSigGenerate.ALILIVE_APPKEY)) {
return AliLiveUserSigGenerate.ALILIVE_APPKEY;
}
if (!TextUtils.isEmpty(SharedPreferenceUtils.getAppKey(ContextUtils.getApplicationContext()))) {
return SharedPreferenceUtils.getAppKey(ContextUtils.getApplicationContext());
}
if (!TextUtils.isEmpty(BuildConfig.INTERACTIVE_APP_KEY)) {
return BuildConfig.INTERACTIVE_APP_KEY;
}
Context context = ContextUtils.getContext();
if (context != null) {
return context.getString(R.string.interactive_appkey);
}
return AliLiveUserSigGenerate.ALILIVE_APPKEY;
}
/**
* Get test interactive play domain
* <p>
* put your app key into `local.properties`
* such as: interactive.playdomain=pullkeriatest.alivecdn.com
* <p>
* or, you can export it into system environment
* such as: export INTERACTIVE_PLAY_DOMAIN=pullkeriatest.alivecdn.com
*
* @return interactive play domain
*/
public static String getTestInteractivePlayDomain() {
if (!checkIsPlaceholder(AliLiveUserSigGenerate.ALILIVE_PLAY_DOMAIN)) {
return AliLiveUserSigGenerate.ALILIVE_PLAY_DOMAIN;
}
if (!TextUtils.isEmpty(SharedPreferenceUtils.getPlayDomain(ContextUtils.getApplicationContext()))) {
return SharedPreferenceUtils.getPlayDomain(ContextUtils.getApplicationContext());
}
if (!TextUtils.isEmpty(BuildConfig.INTERACTIVE_PLAY_DOMAIN)) {
return BuildConfig.INTERACTIVE_PLAY_DOMAIN;
}
Context context = ContextUtils.getContext();
if (context != null) {
return context.getString(R.string.interactive_play_domain);
}
return AliLiveUserSigGenerate.ALILIVE_PLAY_DOMAIN;
}
/**
* Check whether configuration value is placeholder or not
*
* @param configuration configuration value
* @return true, false
*/
public static boolean checkIsPlaceholder(String configuration) {
return TextUtils.isEmpty(configuration) || TextUtils.equals(configuration, PLACEHOLDER);
}
}

View File

@@ -0,0 +1,86 @@
package com.alivc.live.commonbiz.testapi;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.GLUtils;
import android.util.Log;
/**
* @author keria
* @date 2024/7/5
* @brief FIXME keria:测试代码
*/
public class EGLContextTest {
private static final String TAG = "EGLContextTest";
private EGLContextTest() {
}
/**
* 创建 EGLContext
*/
public static void testGLContext() {
int maxContexts = 0;
// 假设尝试创建最多1000个上下文
EGLContext[] contexts = new EGLContext[1000];
EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
Log.e(TAG, "Unable to get EGL14 display");
return;
}
int[] version = new int[2];
if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
Log.e(TAG, "Unable to initialize EGL14");
return;
}
int[] configAttribs = {
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_DEPTH_SIZE, 24,
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
if (!EGL14.eglChooseConfig(eglDisplay, configAttribs, 0, configs, 0, 1, numConfigs, 0)) {
Log.e(TAG, "Unable to choose config");
return;
}
int[] contextAttribs = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
for (int i = 0; i < contexts.length; i++) {
contexts[i] = EGL14.eglCreateContext(eglDisplay, configs[0], EGL14.EGL_NO_CONTEXT, contextAttribs, 0);
if (contexts[i] == EGL14.EGL_NO_CONTEXT) {
Log.e(TAG, "Failed to create EGL context at index " + i + ": " + EGL14.eglGetError() + " - " + GLUtils.getEGLErrorString(EGL14.eglGetError()));
break;
}
maxContexts++;
}
Log.d(TAG, "Maximum number of OpenGL ES contexts supported: " + maxContexts);
// 清理所有创建的上下文
for (int i = 0; i < maxContexts; i++) {
if (contexts[i] != EGL14.EGL_NO_CONTEXT) {
EGL14.eglDestroyContext(eglDisplay, contexts[i]);
}
}
EGL14.eglTerminate(eglDisplay);
}
}

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="30dp"
android:gravity="center"
android:text="@string/backdoor_title"
android:textColor="#747A8C"
android:textSize="20dp" />
<TextView
android:id="@+id/tv_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:gravity="center"
android:text="@string/backdoor_tip"
android:textColor="#B2B7C4"
android:textSize="11dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:background="#3A3D48"
android:orientation="vertical"
android:padding="10dp">
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/sw_force_rtc_pre_env"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.alivc.live.commonui.widgets.LivePushTextSwitch
android:id="@+id/sw_multi_pk_16in"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="6dp">
<TextView
android:id="@+id/info_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_vertical"
android:lineSpacingMultiplier="1.5"
android:padding="6dp"
android:textSize="12dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="backdoor_title">Live Push Demo Configuration Page</string>
<string name="backdoor_tip">Used to manually configure demo corresponding functions</string>
<string name="backdoor_rtc_force_pre_env">Force RTC Pre Environment</string>
<string name="backdoor_multi_pk_16in">Multi PK 16IN</string>
</resources>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="backdoor_title">Live Push Demo配置页面</string>
<string name="backdoor_tip">用于手动配置demo相应功能</string>
<string name="backdoor_rtc_force_pre_env">强制RTC预发环境</string>
<string name="backdoor_multi_pk_16in">16方多人PK</string>
</resources>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="backdoor_title">Live Push Demo Configuration Page</string>
<string name="backdoor_tip">Used to manually configure demo corresponding functions</string>
<string name="backdoor_rtc_force_pre_env">Force RTC Pre Environment</string>
<string name="backdoor_multi_pk_16in">Multi PK 16IN</string>
</resources>