zhibo/android-app/app/src/main/java/com/example/livestreaming/BroadcastActivity.java
2026-01-04 20:50:37 +08:00

810 lines
30 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
// 推流参数 - 平衡画质和流畅度
private static final int VIDEO_WIDTH = 720;
private static final int VIDEO_HEIGHT = 480;
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;
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);
// 先开始预览,再准备编码器(推流时再准备)
String cameraId = isFrontCamera ? "1" : "0";
rtmpCamera2.startPreview(cameraId);
useCamera2 = true;
cameraInitialized = true;
Log.d(TAG, "Camera2 预览已开始");
return;
} catch (Exception e) {
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);
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) {
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 {
if (useCamera2 && rtmpCamera2 != null) {
Log.d(TAG, "使用 Camera2 API 推流");
// 推流前准备编码器 - 使用优化后的低码率参数
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 推流");
// 推流前准备编码器 - 使用优化参数
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();
}
}
}