zhibo/android-app/app/src/main/java/com/example/livestreaming/BroadcastActivity.java

810 lines
30 KiB
Java
Raw Normal View History

package com.example.livestreaming;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.util.Size;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.example.livestreaming.databinding.ActivityBroadcastBinding;
import com.example.livestreaming.net.ApiClient;
import com.example.livestreaming.net.ApiResponse;
import com.example.livestreaming.net.AuthStore;
import com.example.livestreaming.net.CreateRoomRequest;
import com.example.livestreaming.net.Room;
import com.pedro.encoder.input.video.CameraHelper;
import com.pedro.rtmp.utils.ConnectCheckerRtmp;
import com.pedro.rtplibrary.rtmp.RtmpCamera1;
import com.pedro.rtplibrary.rtmp.RtmpCamera2;
import java.util.Locale;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* 手机开播界面
* 使用 RootEncoder 进行 RTMP 推流
* 优先使用 Camera2 API (RtmpCamera2)兼容性更好
*/
public class BroadcastActivity extends AppCompatActivity implements ConnectCheckerRtmp, SurfaceHolder.Callback {
private static final String TAG = "BroadcastActivity";
private static final int REQUEST_PERMISSIONS = 100;
private static final String[] REQUIRED_PERMISSIONS = {
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
};
private ActivityBroadcastBinding binding;
// 使用 Camera2 API 的推流器
private RtmpCamera2 rtmpCamera2;
// 备用:使用 Camera1 API 的推流器
private RtmpCamera1 rtmpCamera1;
private boolean useCamera2 = true;
private Room currentRoom;
private boolean isStreaming = false;
private boolean isFrontCamera = true;
private boolean surfaceReady = false;
private boolean streamerVerified = false;
private boolean cameraInitialized = false;
2026-01-04 20:50:37 +08:00
// 推流参数 - 平衡画质和流畅度
private static final int VIDEO_WIDTH = 720;
private static final int VIDEO_HEIGHT = 480;
2026-01-04 20:50:37 +08:00
private static final int VIDEO_FPS = 24;
private static final int VIDEO_BITRATE = 1200 * 1024; // 1.2Mbps
private static final int AUDIO_BITRATE = 64 * 1024;
2026-01-04 20:50:37 +08:00
private static final int AUDIO_SAMPLE_RATE = 44100;
// 直播计时
private Handler timerHandler = new Handler(Looper.getMainLooper());
private Handler mainHandler = new Handler(Looper.getMainLooper());
private long startTime = 0;
private Runnable timerRunnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 保持屏幕常亮
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
binding = ActivityBroadcastBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 检查登录状态
if (!AuthHelper.isLoggedIn(this)) {
Toast.makeText(this, "请先登录", Toast.LENGTH_SHORT).show();
finish();
return;
}
setupUI();
setupSurface();
// 先检查主播资格,通过后再检查权限
checkStreamerStatus();
}
/**
* 检查主播资格
*/
private void checkStreamerStatus() {
binding.progressBar.setVisibility(View.VISIBLE);
binding.btnStartLive.setEnabled(false);
ApiClient.getService(this).checkStreamerStatus()
.enqueue(new Callback<ApiResponse<Map<String, Object>>>() {
@Override
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
Response<ApiResponse<Map<String, Object>>> response) {
binding.progressBar.setVisibility(View.GONE);
if (!response.isSuccessful() || response.body() == null) {
// 接口调用失败,可能是旧版本后端,允许继续
Log.w(TAG, "检查主播资格接口失败,允许继续");
streamerVerified = true;
binding.btnStartLive.setEnabled(true);
checkPermissions();
return;
}
ApiResponse<Map<String, Object>> body = response.body();
if (body.getCode() != 200 || body.getData() == null) {
// 接口返回错误,可能是旧版本后端,允许继续
Log.w(TAG, "检查主播资格返回错误,允许继续");
streamerVerified = true;
binding.btnStartLive.setEnabled(true);
checkPermissions();
return;
}
Map<String, Object> data = body.getData();
Boolean isStreamer = data.get("isStreamer") != null && (Boolean) data.get("isStreamer");
Boolean isBanned = data.get("isBanned") != null && (Boolean) data.get("isBanned");
Boolean hasApplication = data.get("hasApplication") != null && (Boolean) data.get("hasApplication");
Object appStatusObj = data.get("applicationStatus");
Integer applicationStatus = appStatusObj != null ? ((Number) appStatusObj).intValue() : null;
if (isBanned) {
// 被封禁
String banReason = (String) data.get("banReason");
showBlockedDialog("您的主播资格已被封禁" + (banReason != null ? "" + banReason : ""));
return;
}
if (!isStreamer) {
// 不是主播
if (hasApplication && applicationStatus != null && applicationStatus == 0) {
// 有待审核的申请
showBlockedDialog("您的主播认证申请正在审核中,请耐心等待");
} else {
// 没有申请或申请被拒绝,提示申请认证
showApplyStreamerDialog();
}
return;
}
// 是认证主播,可以开播
streamerVerified = true;
binding.btnStartLive.setEnabled(true);
checkPermissions();
}
@Override
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
binding.progressBar.setVisibility(View.GONE);
// 网络错误,可能是旧版本后端,允许继续
Log.w(TAG, "检查主播资格网络错误,允许继续", t);
streamerVerified = true;
binding.btnStartLive.setEnabled(true);
checkPermissions();
}
});
}
/**
* 显示被阻止的对话框
*/
private void showBlockedDialog(String message) {
new AlertDialog.Builder(this)
.setTitle("无法开播")
.setMessage(message)
.setPositiveButton("确定", (dialog, which) -> finish())
.setCancelable(false)
.show();
}
/**
* 显示申请主播认证的对话框
*/
private void showApplyStreamerDialog() {
new AlertDialog.Builder(this)
.setTitle("需要主播认证")
.setMessage("只有认证主播才能开播,是否现在申请主播认证?")
.setPositiveButton("去申请", (dialog, which) -> {
// 跳转到主播认证申请页面
Intent intent = new Intent(this, StreamerApplyActivity.class);
startActivity(intent);
finish();
})
.setNegativeButton("取消", (dialog, which) -> finish())
.setCancelable(false)
.show();
}
private void setupUI() {
// 关闭按钮
binding.btnClose.setOnClickListener(v -> {
if (isStreaming) {
showStopConfirmDialog();
} else {
finish();
}
});
// 切换摄像头
binding.btnSwitchCamera.setOnClickListener(v -> switchCamera());
// 设置按钮
binding.btnSettings.setOnClickListener(v -> {
Toast.makeText(this, "设置功能开发中", Toast.LENGTH_SHORT).show();
});
// 开始直播
binding.btnStartLive.setOnClickListener(v -> startLive());
// 停止直播
binding.btnStopLive.setOnClickListener(v -> showStopConfirmDialog());
}
private void setupSurface() {
binding.surfaceView.getHolder().addCallback(this);
}
private void checkPermissions() {
boolean allGranted = true;
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
initCamera();
} else {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_PERMISSIONS);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSIONS) {
boolean allGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
initCamera();
} else {
Toast.makeText(this, "需要摄像头和麦克风权限才能开播", Toast.LENGTH_LONG).show();
finish();
}
}
}
private void initCamera() {
if (!surfaceReady) {
Log.d(TAG, "Surface 未就绪,等待...");
return;
}
if (cameraInitialized) {
Log.d(TAG, "摄像头已初始化");
return;
}
// 检查是否有摄像头
if (!hasCamera()) {
Log.e(TAG, "设备没有摄像头");
Toast.makeText(this, "设备没有摄像头,无法开播", Toast.LENGTH_LONG).show();
return;
}
Log.d(TAG, "开始初始化摄像头...");
// 延迟初始化,确保 Surface 完全准备好
mainHandler.postDelayed(() -> {
try {
initCameraInternal();
} catch (Exception e) {
Log.e(TAG, "摄像头初始化异常: " + e.getMessage(), e);
Toast.makeText(this, "摄像头初始化失败", Toast.LENGTH_LONG).show();
}
}, 500);
}
private void initCameraInternal() {
// 优先尝试 Camera2 API (Android 5.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
Log.d(TAG, "尝试使用 Camera2 API...");
rtmpCamera2 = new RtmpCamera2(binding.surfaceView, this);
2026-01-04 20:50:37 +08:00
// 先开始预览,再准备编码器(推流时再准备)
String cameraId = isFrontCamera ? "1" : "0";
rtmpCamera2.startPreview(cameraId);
useCamera2 = true;
cameraInitialized = true;
Log.d(TAG, "Camera2 预览已开始");
return;
} catch (Exception e) {
2026-01-04 20:50:37 +08:00
Log.w(TAG, "Camera2 初始化失败: " + e.getMessage(), e);
if (rtmpCamera2 != null) {
try { rtmpCamera2.stopPreview(); } catch (Exception ignored) {}
}
rtmpCamera2 = null;
}
}
// 回退到 Camera1 API
try {
Log.d(TAG, "尝试使用 Camera1 API...");
rtmpCamera1 = new RtmpCamera1(binding.surfaceView, this);
2026-01-04 20:50:37 +08:00
CameraHelper.Facing facing = isFrontCamera ? CameraHelper.Facing.FRONT : CameraHelper.Facing.BACK;
rtmpCamera1.startPreview(facing);
useCamera2 = false;
cameraInitialized = true;
Log.d(TAG, "Camera1 预览已开始");
return;
} catch (Exception e) {
2026-01-04 20:50:37 +08:00
Log.e(TAG, "Camera1 初始化也失败: " + e.getMessage(), e);
if (rtmpCamera1 != null) {
try { rtmpCamera1.stopPreview(); } catch (Exception ignored) {}
}
rtmpCamera1 = null;
}
Toast.makeText(this, "摄像头初始化失败,请检查权限或重启应用", Toast.LENGTH_LONG).show();
}
/**
* 检查设备是否有摄像头
*/
private boolean hasCamera() {
return getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
}
private void switchCamera() {
isFrontCamera = !isFrontCamera;
if (useCamera2 && rtmpCamera2 != null) {
try {
String cameraId = isFrontCamera ? "1" : "0";
rtmpCamera2.switchCamera();
Toast.makeText(this, isFrontCamera ? "前置摄像头" : "后置摄像头", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.e(TAG, "切换摄像头失败: " + e.getMessage());
}
} else if (rtmpCamera1 != null) {
try {
rtmpCamera1.switchCamera();
Toast.makeText(this, isFrontCamera ? "前置摄像头" : "后置摄像头", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.e(TAG, "切换摄像头失败: " + e.getMessage());
}
}
}
private void startLive() {
// 检查主播资格是否已验证
if (!streamerVerified) {
Toast.makeText(this, "正在验证主播资格...", Toast.LENGTH_SHORT).show();
checkStreamerStatus();
return;
}
String title = binding.etTitle.getText() != null ?
binding.etTitle.getText().toString().trim() : "";
if (TextUtils.isEmpty(title)) {
Toast.makeText(this, "请输入直播标题", Toast.LENGTH_SHORT).show();
return;
}
if (!cameraInitialized) {
Toast.makeText(this, "摄像头未初始化,请稍候", Toast.LENGTH_SHORT).show();
return;
}
binding.progressBar.setVisibility(View.VISIBLE);
binding.btnStartLive.setEnabled(false);
// 先创建直播间
createRoom(title);
}
private void createRoom(String title) {
String nickname = AuthStore.getNickname(this);
if (TextUtils.isEmpty(nickname)) {
nickname = "主播";
}
CreateRoomRequest request = new CreateRoomRequest();
request.setTitle(title);
request.setStreamerName(nickname);
ApiClient.getService(this).createRoom(request).enqueue(new Callback<ApiResponse<Room>>() {
@Override
public void onResponse(Call<ApiResponse<Room>> call, Response<ApiResponse<Room>> response) {
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
currentRoom = response.body().getData();
Log.d(TAG, "直播间创建成功: " + currentRoom.getId());
startStreaming();
} else {
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
String msg = response.body() != null ? response.body().getMessage() : "创建直播间失败";
Toast.makeText(BroadcastActivity.this, msg, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<Room>> call, Throwable t) {
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
Toast.makeText(BroadcastActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
private void startStreaming() {
if (currentRoom == null) {
Log.e(TAG, "currentRoom 为空");
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
Toast.makeText(this, "获取房间信息失败", Toast.LENGTH_SHORT).show();
return;
}
if (currentRoom.getStreamUrls() == null) {
Log.e(TAG, "streamUrls 为空, roomId=" + currentRoom.getId() + ", streamKey=" + currentRoom.getStreamKey());
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
Toast.makeText(this, "获取推流地址失败", Toast.LENGTH_SHORT).show();
return;
}
String rtmpUrl = currentRoom.getStreamUrls().getRtmp();
String flvUrl = currentRoom.getStreamUrls().getFlv();
String hlsUrl = currentRoom.getStreamUrls().getHls();
Log.d(TAG, "========== 流地址信息 ==========");
Log.d(TAG, "房间ID: " + currentRoom.getId());
Log.d(TAG, "StreamKey: " + currentRoom.getStreamKey());
Log.d(TAG, "RTMP推流地址: " + rtmpUrl);
Log.d(TAG, "FLV播放地址: " + flvUrl);
Log.d(TAG, "HLS播放地址: " + hlsUrl);
Log.d(TAG, "================================");
if (TextUtils.isEmpty(rtmpUrl)) {
Log.e(TAG, "RTMP推流地址为空");
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
Toast.makeText(this, "推流地址无效", Toast.LENGTH_SHORT).show();
return;
}
Log.d(TAG, "开始推流到: " + rtmpUrl);
try {
2026-01-04 20:50:37 +08:00
if (useCamera2 && rtmpCamera2 != null) {
Log.d(TAG, "使用 Camera2 API 推流");
2026-01-04 20:50:37 +08:00
// 推流前准备编码器 - 使用优化后的低码率参数
boolean audioReady = rtmpCamera2.prepareAudio(AUDIO_BITRATE, AUDIO_SAMPLE_RATE, false);
// 使用 640x360 低分辨率,更流畅
boolean videoReady = rtmpCamera2.prepareVideo(VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_FPS, VIDEO_BITRATE, 0);
Log.d(TAG, "编码器准备: audio=" + audioReady + ", video=" + videoReady);
if (!videoReady) {
// 尝试更低的分辨率
Log.w(TAG, "640x360 失败,尝试 480x270");
videoReady = rtmpCamera2.prepareVideo(480, 270, VIDEO_FPS, VIDEO_BITRATE, 0);
}
if (!videoReady) {
Log.e(TAG, "视频编码器准备失败");
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
Toast.makeText(this, "视频编码器初始化失败", Toast.LENGTH_SHORT).show();
return;
}
if (!rtmpCamera2.isStreaming()) {
rtmpCamera2.startStream(rtmpUrl);
}
} else if (rtmpCamera1 != null) {
Log.d(TAG, "使用 Camera1 API 推流");
2026-01-04 20:50:37 +08:00
// 推流前准备编码器 - 使用优化参数
boolean audioReady = rtmpCamera1.prepareAudio(AUDIO_BITRATE, AUDIO_SAMPLE_RATE, false, false, false);
boolean videoReady = rtmpCamera1.prepareVideo(VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_FPS, VIDEO_BITRATE, 0);
Log.d(TAG, "编码器准备: audio=" + audioReady + ", video=" + videoReady);
if (!videoReady) {
Log.e(TAG, "视频编码器准备失败");
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
Toast.makeText(this, "视频编码器初始化失败", Toast.LENGTH_SHORT).show();
return;
}
if (!rtmpCamera1.isStreaming()) {
rtmpCamera1.startStream(rtmpUrl);
}
} else {
Log.e(TAG, "没有可用的摄像头推流器");
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
Toast.makeText(this, "摄像头未初始化", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Log.e(TAG, "推流失败: " + e.getMessage(), e);
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
Toast.makeText(this, "推流失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
private void stopStreaming() {
try {
if (useCamera2 && rtmpCamera2 != null && rtmpCamera2.isStreaming()) {
rtmpCamera2.stopStream();
} else if (rtmpCamera1 != null && rtmpCamera1.isStreaming()) {
rtmpCamera1.stopStream();
}
} catch (Exception e) {
Log.e(TAG, "停止推流失败: " + e.getMessage());
}
isStreaming = false;
stopTimer();
updateUI(false);
// 删除直播间
if (currentRoom != null) {
ApiClient.getService(this).deleteRoom(currentRoom.getId()).enqueue(new Callback<ApiResponse<Object>>() {
@Override
public void onResponse(Call<ApiResponse<Object>> call, Response<ApiResponse<Object>> response) {
Log.d(TAG, "直播间已删除");
}
@Override
public void onFailure(Call<ApiResponse<Object>> call, Throwable t) {
Log.e(TAG, "删除直播间失败: " + t.getMessage());
}
});
currentRoom = null;
}
}
private void showStopConfirmDialog() {
new com.google.android.material.dialog.MaterialAlertDialogBuilder(this)
.setTitle("结束直播")
.setMessage("确定要结束直播吗?")
.setPositiveButton("结束", (dialog, which) -> {
stopStreaming();
finish();
})
.setNegativeButton("取消", null)
.show();
}
private void updateUI(boolean streaming) {
if (streaming) {
binding.cardLiveInfo.setVisibility(View.GONE);
binding.btnStartLive.setVisibility(View.GONE);
binding.btnStopLive.setVisibility(View.VISIBLE);
binding.liveStatusBar.setVisibility(View.VISIBLE);
} else {
binding.cardLiveInfo.setVisibility(View.VISIBLE);
binding.btnStartLive.setVisibility(View.VISIBLE);
binding.btnStopLive.setVisibility(View.GONE);
binding.liveStatusBar.setVisibility(View.GONE);
}
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
}
private void startTimer() {
startTime = System.currentTimeMillis();
timerRunnable = new Runnable() {
@Override
public void run() {
long elapsed = System.currentTimeMillis() - startTime;
int seconds = (int) (elapsed / 1000) % 60;
int minutes = (int) (elapsed / 1000 / 60) % 60;
int hours = (int) (elapsed / 1000 / 60 / 60);
binding.tvLiveTime.setText(String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds));
timerHandler.postDelayed(this, 1000);
}
};
timerHandler.post(timerRunnable);
}
private void stopTimer() {
if (timerRunnable != null) {
timerHandler.removeCallbacks(timerRunnable);
timerRunnable = null;
}
}
// ========== SurfaceHolder.Callback ==========
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
Log.d(TAG, "Surface created");
surfaceReady = true;
initCamera();
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG, "Surface changed: " + width + "x" + height);
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
Log.d(TAG, "Surface destroyed");
surfaceReady = false;
try {
if (rtmpCamera2 != null) {
if (rtmpCamera2.isStreaming()) {
rtmpCamera2.stopStream();
}
if (rtmpCamera2.isOnPreview()) {
rtmpCamera2.stopPreview();
}
}
if (rtmpCamera1 != null) {
if (rtmpCamera1.isStreaming()) {
rtmpCamera1.stopStream();
}
if (rtmpCamera1.isOnPreview()) {
rtmpCamera1.stopPreview();
}
}
} catch (Exception e) {
Log.e(TAG, "停止预览失败: " + e.getMessage());
}
}
// ========== ConnectCheckerRtmp 回调 ==========
@Override
public void onConnectionStartedRtmp(String rtmpUrl) {
Log.d(TAG, "========== RTMP连接开始 ==========");
Log.d(TAG, "正在连接: " + rtmpUrl);
}
@Override
public void onConnectionSuccessRtmp() {
runOnUiThread(() -> {
Log.d(TAG, "========== RTMP连接成功 ==========");
Log.d(TAG, "推流已成功连接到服务器");
isStreaming = true;
updateUI(true);
startTimer();
Toast.makeText(this, "直播已开始", Toast.LENGTH_SHORT).show();
});
}
@Override
public void onConnectionFailedRtmp(String reason) {
runOnUiThread(() -> {
Log.e(TAG, "========== RTMP连接失败 ==========");
Log.e(TAG, "失败原因: " + reason);
Log.e(TAG, "请检查:");
Log.e(TAG, "1. SRS服务器是否运行");
Log.e(TAG, "2. RTMP端口(1935/25002)是否开放");
Log.e(TAG, "3. 手机网络是否能访问服务器");
isStreaming = false;
updateUI(false);
Toast.makeText(this, "连接失败: " + reason, Toast.LENGTH_LONG).show();
});
}
@Override
public void onNewBitrateRtmp(long bitrate) {
// 码率变化回调,可用于显示当前上传速度
Log.v(TAG, "当前码率: " + (bitrate / 1024) + " kbps");
}
@Override
public void onDisconnectRtmp() {
runOnUiThread(() -> {
Log.d(TAG, "推流断开");
if (isStreaming) {
isStreaming = false;
updateUI(false);
Toast.makeText(this, "直播已断开", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAuthErrorRtmp() {
runOnUiThread(() -> {
Log.e(TAG, "推流认证失败");
Toast.makeText(this, "推流认证失败", Toast.LENGTH_SHORT).show();
});
}
@Override
public void onAuthSuccessRtmp() {
Log.d(TAG, "推流认证成功");
}
@Override
protected void onResume() {
super.onResume();
if (cameraInitialized && surfaceReady && !isStreaming) {
try {
if (useCamera2 && rtmpCamera2 != null && !rtmpCamera2.isOnPreview()) {
String cameraId = isFrontCamera ? "1" : "0";
rtmpCamera2.startPreview(cameraId);
} else if (rtmpCamera1 != null && !rtmpCamera1.isOnPreview()) {
CameraHelper.Facing facing = isFrontCamera ? CameraHelper.Facing.FRONT : CameraHelper.Facing.BACK;
rtmpCamera1.startPreview(facing);
}
} catch (Exception e) {
Log.e(TAG, "恢复预览失败: " + e.getMessage());
}
}
}
@Override
protected void onPause() {
super.onPause();
// 如果正在直播,不停止预览
if (!isStreaming) {
try {
if (rtmpCamera2 != null && rtmpCamera2.isOnPreview()) {
rtmpCamera2.stopPreview();
}
if (rtmpCamera1 != null && rtmpCamera1.isOnPreview()) {
rtmpCamera1.stopPreview();
}
} catch (Exception e) {
Log.e(TAG, "暂停预览失败: " + e.getMessage());
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopTimer();
try {
if (rtmpCamera2 != null) {
if (rtmpCamera2.isStreaming()) {
rtmpCamera2.stopStream();
}
if (rtmpCamera2.isOnPreview()) {
rtmpCamera2.stopPreview();
}
}
if (rtmpCamera1 != null) {
if (rtmpCamera1.isStreaming()) {
rtmpCamera1.stopStream();
}
if (rtmpCamera1.isOnPreview()) {
rtmpCamera1.stopPreview();
}
}
} catch (Exception e) {
Log.e(TAG, "销毁时清理失败: " + e.getMessage());
}
}
@Override
public void onBackPressed() {
if (isStreaming) {
showStopConfirmDialog();
} else {
super.onBackPressed();
}
}
}