MainActivity.java:package com.videogo.ui.login;
import android.content.res.Configuration;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.videogo.exception.BaseException;
import com.videogo.exception.ErrorCode;
import com.videogo.openapi.EZConstants;
import com.videogo.openapi.EZOpenSDK;
import com.videogo.openapi.EZPlayer;
import ezviz.ezopensdk.R;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.LinearLayout;
import android.widget.ImageButton;
import android.widget.Toast;
import android.view.MotionEvent;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import org.json.JSONObject;
import android.content.Intent;
import android.widget.Button;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, Handler.Callback {
private static final String TAG = "EZPreview";
private static final int MSG_VIDEO_SIZE_CHANGED = 1;
private static final int MSG_REALPLAY_PLAY_SUCCESS = 2001;
private static final int MSG_REALPLAY_PLAY_FAIL = 2002;
private static final int MSG_SHOW_STREAM_TYPE = 3001; // 新增消息类型
private static final int DIRECTION_UP = 0;
private static final int DIRECTION_DOWN = 1;
private static final int DIRECTION_LEFT = 2;
private static final int DIRECTION_RIGHT = 3;
private static final int DIRECTION_ZOOM_IN = 8; // 物理放大
private static final int DIRECTION_ZOOM_OUT = 9; // 物理缩小
private static final int DIRECTION_FOCUS_NEAR = 10; // 调整近焦距
private static final int DIRECTION_FOCUS_FAR = 11; // 调整远焦距
private static final int SPEED_MEDIUM = 1; // 速度适中
private static final String PTZ_START_URL = "https://open.ys7.com/api/lapp/device/ptz/start";
private static final String PTZ_STOP_URL = "https://open.ys7.com/api/lapp/device/ptz/stop";
private boolean mIsSupportPTZ = false; // 设备是否支持云台
private ExecutorService mExecutorService; // 线程池
private volatile boolean isPTZActive = false; // 标记是否有云台操作正在进行
private volatile int activeDirection = -1; // 当前活动的方向
private boolean mHasShownConnectionSuccess = false;
// 接收的参数键
private static final String KEY_APPKEY = "appkey";
private static final String KEY_SERIAL = "serial";
private static final String KEY_VERIFYCODE = "VerifyCode";
private static final String KEY_ACCESSTOKEN = "accessToken";
private static final String KEY_CAMERANO = "cameraNo";
private boolean mIsPlaying = false;
private EZPlayer mEZPlayer;
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private ProgressBar mLiveProgressBar;
private RelativeLayout mRlControl;
private LinearLayout mLlHc;
private ImageButton mIbRotate2;
private RelativeLayout mRaTitle;
// 从Intent中获取的参数
private String mAppKey;
private String mDeviceSerial;
private String mVerifyCode;
private String mAccessToken;
private int mCameraNo = 0; // 默认通道号0
private Handler mHandler;
private boolean mP2PEnabled = true;
// 新增:云台控制重试机制相关变量
private static final int MAX_PTZ_RETRIES = 2;
private Map<Integer, Integer> ptzRetryCountMap = new ConcurrentHashMap<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activitymain);
mHandler = new Handler(this);
mRaTitle = findViewById(R.id.ra_title);
// 1. 从Intent中获取参数
extractParametersFromIntent();
// 2. 初始化UI
initUI();
initOrientationSensitiveViews();
View fanHui = findViewById(R.id.back);
fanHui.setOnClickListener(v -> finish());
Button huifangBtn = findViewById(R.id.huifang);
huifangBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建Intent跳转到FanHui活动
Intent intent = new Intent(MainActivity.this, FanHui.class);
// 传递必要参数(可选)
intent.putExtra("deviceSerial", mDeviceSerial);
intent.putExtra("cameraNo", mCameraNo);
intent.putExtra("accessToken", mAccessToken);
intent.putExtra("appkey", mAppKey);
intent.putExtra("verifyCode", mVerifyCode);
startActivity(intent);
}
});
// 3. 初始化SDK并创建播放器
initSDKAndCreatePlayer();
}
private void initOrientationSensitiveViews() {
mLiveProgressBar = findViewById(R.id.liveProgressBar);
mRlControl = findViewById(R.id.rl_control);
mLlHc = findViewById(R.id.ll_hc);
mIbRotate2 = findViewById(R.id.ib_rotate2);
// 初始状态显示加载
if (mLiveProgressBar != null) {
mLiveProgressBar.setVisibility(View.VISIBLE);
}
// 初始根据方向更新UI
updateLayoutByOrientation();
// 创建线程池
mExecutorService = Executors.newFixedThreadPool(2);
// 4. 初始化云台控制按钮
initPTZButtons();
}
private void initPTZButtons() {
// 竖屏布局的按钮
setupPTZButton(R.id.ptz_left_btn, DIRECTION_LEFT);
setupPTZButton(R.id.ptz_right_btn, DIRECTION_RIGHT);
setupPTZButton(R.id.ptz_top_btn, DIRECTION_UP);
setupPTZButton(R.id.ptz_bottom_btn, DIRECTION_DOWN);
setupPTZButton(R.id.zoom_add, DIRECTION_ZOOM_IN); // 物理放大
setupPTZButton(R.id.zoom_reduce, DIRECTION_ZOOM_OUT); // 物理缩小
setupPTZButton(R.id.focus_add, DIRECTION_FOCUS_NEAR); // 近焦距
setupPTZButton(R.id.foucus_reduce, DIRECTION_FOCUS_FAR); // 远焦距
setupUnsupportedButton(R.id.guangquan_add, "光圈+");
setupUnsupportedButton(R.id.guangquan_reduce, "光圈-");
// 横屏布局的按钮
setupPTZButton(R.id.ptz_left_btn2, DIRECTION_LEFT);
setupPTZButton(R.id.ptz_right_btn2, DIRECTION_RIGHT);
setupPTZButton(R.id.ptz_top_btn2, DIRECTION_UP);
setupPTZButton(R.id.ptz_bottom_btn2, DIRECTION_DOWN);
setupPTZButton(R.id.zoom_add2, DIRECTION_ZOOM_IN);
setupPTZButton(R.id.zoom_reduce2, DIRECTION_ZOOM_OUT);
setupPTZButton(R.id.focus_add2, DIRECTION_FOCUS_NEAR); // 近焦距
setupPTZButton(R.id.foucus_reduce2, DIRECTION_FOCUS_FAR); // 远焦距
}
private void setupUnsupportedButton(int buttonId, String buttonName) {
ImageButton button = findViewById(buttonId);
if (button != null) {
button.setOnClickListener(v -> {
Toast.makeText(MainActivity.this,
"SDK不支持此功能",
Toast.LENGTH_SHORT).show();
});
}
}
private void setupPTZButton(int buttonId, final int direction) {
ImageButton button = findViewById(buttonId);
if (button != null) {
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 按下时开始云台动作
controlPTZ(direction, SPEED_MEDIUM, true);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 如果这是当前活动的方向,则停止
if (isPTZActive && activeDirection == direction) {
controlPTZ(direction, SPEED_MEDIUM, false);
}
return true;
}
return false;
}
});
}
}
// 重构后的云台控制方法
private void controlPTZ(final int direction, final int speed, final boolean isStart) {
// 检查是否允许开始新操作
if (isStart) {
if (isPTZActive) {
// 如果有活动操作,先停止当前操作
controlPTZ(activeDirection, speed, false);
// 延迟100ms后再开始新操作
new Handler().postDelayed(() -> controlPTZ(direction, speed, true), 100);
return;
}
}
// 重试次数检查
Integer retryCount = ptzRetryCountMap.getOrDefault(direction, 0);
if (retryCount >= MAX_PTZ_RETRIES) {
Log.w(TAG, "达到最大重试次数,放弃操作 direction=" + direction);
ptzRetryCountMap.remove(direction);
resetPTZState();
return;
}
mExecutorService.execute(() -> {
try {
String urlStr = isStart ? PTZ_START_URL : PTZ_STOP_URL;
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 构建POST数据
StringBuilder postData = new StringBuilder();
postData.append("accessToken=").append(URLEncoder.encode(mAccessToken, "UTF-8"));
postData.append("&deviceSerial=").append(URLEncoder.encode(mDeviceSerial, "UTF-8"));
postData.append("&channelNo=").append(mCameraNo);
if (isStart) {
postData.append("&direction=").append(direction);
postData.append("&speed=").append(speed);
} else {
// 停止时带上方向参数(建议)
postData.append("&direction=").append(direction);
}
// 发送请求
OutputStream os = conn.getOutputStream();
os.write(postData.toString().getBytes("UTF-8"));
os.flush();
os.close();
// 处理响应
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 读取响应内容
InputStream is = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// 解析JSON响应
JSONObject json = new JSONObject(response.toString());
String code = json.optString("code", "0");
String msg = json.optString("msg", "未知错误");
if ("200".equals(code)) {
Log.d(TAG, "PTZ控制成功: " + (isStart ? "开始" : "停止") +
" | 方向: " + direction);
// 更新状态
if (isStart) {
isPTZActive = true;
activeDirection = direction;
} else {
isPTZActive = false;
activeDirection = -1;
}
// 成功时重置重试计数器
ptzRetryCountMap.remove(direction);
} else {
// 增加重试计数
ptzRetryCountMap.put(direction, retryCount + 1);
if (isStart) {
Log.w(TAG, "PTZ控制失败,尝试重试 direction=" + direction + ", retry=" + (retryCount + 1));
controlPTZ(direction, speed, true);
} else {
// 停止命令失败需要特殊处理
handleStopFailure(direction);
}
handlePTZError(code, msg, isStart);
}
} else {
// 增加重试计数
ptzRetryCountMap.put(direction, retryCount + 1);
if (isStart) {
Log.w(TAG, "PTZ控制失败,尝试重试 direction=" + direction + ", retry=" + (retryCount + 1));
controlPTZ(direction, speed, true);
} else {
// 停止命令失败需要特殊处理
handleStopFailure(direction);
}
Log.e(TAG, "HTTP错误: " + responseCode);
runOnUiThread(() -> Toast.makeText(MainActivity.this,
"网络请求失败: " + responseCode, Toast.LENGTH_SHORT).show());
}
conn.disconnect();
} catch (Exception e) {
// 增加重试计数
ptzRetryCountMap.put(direction, retryCount + 1);
if (isStart && retryCount < MAX_PTZ_RETRIES) {
Log.w(TAG, "PTZ控制异常,尝试重试", e);
controlPTZ(direction, speed, true);
} else {
handleStopFailure(direction);
}
Log.e(TAG, "PTZ控制异常", e);
runOnUiThread(() -> Toast.makeText(MainActivity.this,
"云台控制出错: " + e.getMessage(), Toast.LENGTH_SHORT).show());
}
});
}
// 新增:处理停止失败的情况
private void handleStopFailure(int direction) {
runOnUiThread(() -> {
// 紧急停止:发送不带方向参数的停止命令
emergencyStopPTZ();
Toast.makeText(MainActivity.this,
"云台停止失败,已执行紧急停止",
Toast.LENGTH_SHORT).show();
});
// 重置状态
resetPTZState();
}
// 新增:紧急停止方法(不带方向参数)
private void emergencyStopPTZ() {
mExecutorService.execute(() -> {
try {
URL url = new URL(PTZ_STOP_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 构建不带方向参数的POST数据
StringBuilder postData = new StringBuilder();
postData.append("accessToken=").append(URLEncoder.encode(mAccessToken, "UTF-8"));
postData.append("&deviceSerial=").append(URLEncoder.encode(mDeviceSerial, "UTF-8"));
postData.append("&channelNo=").append(mCameraNo);
// 发送请求
OutputStream os = conn.getOutputStream();
os.write(postData.toString().getBytes("UTF-8"));
os.flush();
os.close();
// 处理响应
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 读取响应内容
InputStream is = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// 解析JSON响应
JSONObject json = new JSONObject(response.toString());
String code = json.optString("code", "0");
if ("200".equals(code)) {
Log.d(TAG, "紧急停止成功");
} else {
Log.w(TAG, "紧急停止失败: " + json.optString("msg", "未知错误"));
}
} else {
Log.e(TAG, "紧急停止HTTP错误: " + responseCode);
}
conn.disconnect();
} catch (Exception e) {
Log.e(TAG, "紧急停止异常", e);
}
});
}
// 新增:重置云台状态
private void resetPTZState() {
isPTZActive = false;
activeDirection = -1;
ptzRetryCountMap.clear();
}
private void handlePTZError(String code, String msg, boolean isStart) {
String errorMessage = msg;
// 根据文档映射错误信息
switch (code) {
case "10001":
errorMessage = "参数错误";
break;
case "10002":
errorMessage = "accessToken异常或过期";
break;
case "20002":
errorMessage = "设备不存在";
break;
case "20007":
errorMessage = "设备不在线";
break;
case "60000":
errorMessage = "设备不支持云台控制";
mIsSupportPTZ = false; // 更新设备支持状态
break;
case "60001":
errorMessage = "用户无云台控制权限";
break;
case "60020":
errorMessage = "不支持该命令";
break;
// 添加其他错误码处理...
}
final String finalMsg = errorMessage;
runOnUiThread(() -> {
Toast.makeText(MainActivity.this,
(isStart ? "开始" : "停止") + "云台控制失败: " + finalMsg,
Toast.LENGTH_LONG).show();
// 对于特定错误,重置云台状态
if ("10002".equals(code) || "60000".equals(code)) {
isPTZActive = false;
activeDirection = -1;
}
});
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// 方向变化时重新初始化视图
initOrientationSensitiveViews();
updateLayoutByOrientation();
}
@Override
protected void onResume() {
super.onResume();
if (mLiveProgressBar != null) {
if (mIsPlaying) {
mLiveProgressBar.setVisibility(View.GONE);
} else {
mLiveProgressBar.setVisibility(View.VISIBLE);
}
}
}
private void updateLayoutByOrientation() {
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
// 竖屏模式
mRaTitle.setVisibility(View.VISIBLE);
mRlControl.setVisibility(View.VISIBLE);
mLlHc.setVisibility(View.GONE);
mIbRotate2.setVisibility(View.GONE);
} else {
// 横屏模式
mRaTitle.setVisibility(View.GONE);
mRlControl.setVisibility(View.GONE);
mLlHc.setVisibility(View.VISIBLE);
mIbRotate2.setVisibility(View.VISIBLE);
}
}
/**
* 从Intent中提取传递的参数
*/
private void extractParametersFromIntent() {
Bundle extras = getIntent().getExtras();
if (extras != null) {
mAppKey = extras.getString(KEY_APPKEY, "");
mDeviceSerial = extras.getString(KEY_SERIAL, "");
mVerifyCode = extras.getString(KEY_VERIFYCODE, "");
mAccessToken = extras.getString(KEY_ACCESSTOKEN, "");
mCameraNo = extras.getInt(KEY_CAMERANO, 0);
Log.d(TAG, "Received parameters:");
Log.d(TAG, "AppKey: " + mAppKey);
Log.d(TAG, "DeviceSerial: " + mDeviceSerial);
Log.d(TAG, "VerifyCode: " + mVerifyCode);
Log.d(TAG, "AccessToken: " + mAccessToken);
Log.d(TAG, "CameraNo: " + mCameraNo);
} else {
Log.e(TAG, "No parameters received from intent");
// 如果没有参数,可以显示错误信息并退出
finish();
}
}
/**
* 初始化UI组件
*/
private void initUI() {
mSurfaceView = findViewById(R.id.realplay_sv);
if (mSurfaceView != null) {
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
} else {
Log.e(TAG, "SurfaceView not found with ID realplay_sv");
}
}
/**
* 初始化SDK并创建播放器
*/
private void initSDKAndCreatePlayer() {
try {
// 1. 初始化SDK
EZOpenSDK.initLib(getApplication(), mAppKey);
EZOpenSDK.getInstance().setAccessToken(mAccessToken);
// +++ 开启P2P取流方式 +++
EZOpenSDK.enableP2P(true); // 开启P2P取流
Log.d(TAG, "P极取流已启用");
mP2PEnabled = true;
// 2. 创建播放器
createPlayer();
} catch (Exception e) {
Log.e(TAG, "SDK初始化失败", e);
Toast.makeText(this, "SDK初始化失败", Toast.LENGTH_SHORT).show();
}
}
/**
* 创建播放器并开始播放
*/
private void createPlayer() {
try {
// 1. 创建播放器实例
mEZPlayer = EZOpenSDK.getInstance().createPlayer(mDeviceSerial, mCameraNo);
// 2. 配置播放器
mEZPlayer.setHandler(mHandler);
if (mSurfaceHolder != null) {
mEZPlayer.setSurfaceHold(mSurfaceHolder);
}
if (mVerifyCode != null && !mVerifyCode.isEmpty()) {
mEZPlayer.setPlayVerifyCode(mVerifyCode);
}
Toast.makeText(MainActivity.this, "连接成功", Toast.LENGTH_SHORT).show();
// 3. 开始播放
mEZPlayer.startRealPlay();
mIsPlaying = true; // 标记为正在播放
} catch (Exception e) {
Log.e(TAG, "Player creation failed", e);
mIsPlaying = false; // 标记为未播放
}
}
// 处理屏幕旋转按钮点击
public void changeScreen(View view) {
Log.d(TAG, "Change screen orientation requested");
int currentOrientation = getResources().getConfiguration().orientation;
if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
// 更新UI布局
updateLayoutByOrientation();
}
// Surface回调接口实现
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
if (mEZPlayer != null) {
mEZPlayer.setSurfaceHold(holder);
}
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
if (mEZPlayer != null) {
mEZPlayer.setSurfaceHold(null);
}
}
@Override
protected void onStop() {
super.onStop();
// 确保云台停止
if (isPTZActive && activeDirection != -1) {
Log.d(TAG, "Activity停止,强制停止云台");
controlPTZ(activeDirection, SPEED_MEDIUM, false);
}
if (mEZPlayer != null) {
mEZPlayer.stopRealPlay();
mIsPlaying = false; // 标记为已停止
}
}
// Handler回调处理播放状态
@Override
public boolean handleMessage(@NonNull Message msg) {
Log.d(TAG, "handleMessage: " + msg.what);
switch (msg.what) {
case MSG_VIDEO_SIZE_CHANGED:
break;
case MSG_REALPLAY_PLAY_SUCCESS:
Log.i(TAG, "播放成功");
mIsPlaying = true;
// 获取并显示取流方式
int streamType = mEZPlayer.getStreamFetchType();
String streamTypeName = getStreamTypeName(streamType);
Log.d(TAG, "当前取流方式: " + streamTypeName);
// 发送消息显示取流方式
Message showMsg = new Message();
showMsg.what = MSG_SHOW_STREAM_TYPE;
showMsg.obj = streamTypeName;
mHandler.sendMessage(showMsg);
runOnUiThread(() -> {
if (mLiveProgressBar != null) {
mLiveProgressBar.setVisibility(View.GONE);
}
if (!mHasShownConnectionSuccess) {
mHasShownConnectionSuccess = true;
}
});
// 在播放成功后检查设备是否支持云台
checkDevicePTZSupport();
break;
case MSG_REALPLAY_PLAY_FAIL:
Log.e(TAG, "播放失败");
mIsPlaying = false;
runOnUiThread(() -> {
if (mLiveProgressBar != null) {
mLiveProgressBar.setVisibility(View.VISIBLE);
}
});
BaseException error = (BaseException) msg.obj;
int errorCode = error.getErrorCode();
if (errorCode == ErrorCode.ERROR_INNER_VERIFYCODE_NEED ||
errorCode == ErrorCode.ERROR_INNER_VERIFYCODE_ERROR) {
mVerifyCode = "123456";
if (mEZPlayer != null) {
mEZPlayer.setPlayVerifyCode(mVerifyCode);
mEZPlayer.startRealPlay();
}
} else {
Log.e(TAG, "播放失败,错误码: " + errorCode);
}
break;
case MSG_SHOW_STREAM_TYPE:
String type = (String) msg.obj;
Toast.makeText(MainActivity.this,
"取流方式: " + type + (mP2PEnabled ? " (P2P已启用)" : ""),
Toast.LENGTH_LONG).show();
break;
}
return true;
}
private void checkDevicePTZSupport() {
new Thread(() -> {
try {
// 这里使用SDK方法检查设备能力
// 如果SDK没有提供方法,可以使用HTTP API查询设备能力
// 简化处理:假设所有设备都支持云台
mIsSupportPTZ = true;
// 实际项目中应该查询设备能力
// mIsSupportPTZ = queryDevicePTZCapability();
} catch (Exception e) {
Log.e(TAG, "Failed to check PTZ support", e);
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mExecutorService != null) {
mExecutorService.shutdown();
}
// 移除Handler回调避免内存泄漏
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
if (mEZPlayer != null) {
mEZPlayer.release();
mEZPlayer = null;
}
}
private String getStreamTypeName(int type) {
switch (type) {
case 0: return "流媒体";
case 1: return "P2P";
case 2: return "内网直连";
case 4: return "云存储";
default: return "未知(" + type + ")";
}
}
}
FanHui.java:package com.videogo.ui.login;
import android.app.AlertDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.DatePicker;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.content.res.Configuration;
import com.videogo.exception.BaseException;
import com.videogo.exception.ErrorCode;
import com.videogo.openapi.EZPlayer;
import androidx.appcompat.app.AppCompatActivity;
import com.videogo.openapi.EZOpenSDK;
import ezviz.ezopensdk.R;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import android.content.pm.ActivityInfo;
import com.videogo.widget.CheckTextButton;
public class FanHui extends AppCompatActivity implements SurfaceHolder.Callback {
private static final String TAG = "EZPlayback";
private String mAppKey;
private String mDeviceSerial;
private String mVerifyCode;
private String mAccessToken;
private int mCameraNo;
private TextView mDateTextView;
private int mSelectedYear, mSelectedMonth, mSelectedDay;
private static final String KEY_APPKEY = "appkey";
private static final String KEY_SERIAL = "deviceSerial";
private static final String KEY_VERIFYCODE = "verifyCode";
private static final String KEY_ACCESSTOKEN = "accessToken";
private static final String KEY_CAMERANO = "cameraNo";
private static final int MSG_PLAYBACK_PLAY_SUCCESS = 3001;
private static final int MSG_PLAYBACK_PLAY_FAIL = 3002;
private static final int MSG_PLAYBACK_STOP = 3003;
// 更新API地址为云存储专用接口
private static final String VIDEO_BY_TIME_URL = "https://open.ys7.com/api/lapp/video/by/time";
// private static final String PLAYBACK_URL_API = "https://open.ys7.com/api/lapp/v2/live/address/get";
private boolean isFullScreen = false;
private com.videogo.widget.TitleBar mTitleBar;
private ViewGroup mPlaybackArea;
private LinearLayout mControlArea;
private RelativeLayout mDisplayLayout;
private ImageButton mExitFullScreenButton;
private ExecutorService mExecutorService;
private ListView mListView;
private PlaybackAdapter mAdapter;
private List<VideoInfo> mVideoList = new ArrayList<>();
private LinearLayout mNoVideoLayout;
// 播放器相关
private EZPlayer mEZPlayer;
private SurfaceView mPlaybackSurfaceView;
private SurfaceHolder mSurfaceHolder;
private boolean isVideoPlaying = false; // 跟踪视频播放状态
private ImageButton mPauseButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ez_playback_list_page);
View fanHui = findViewById(R.id.fanhui);
fanHui.setOnClickListener(v -> finish());
// 创建线程池
mExecutorService = Executors.newFixedThreadPool(2);
extractParametersFromIntent();
final Calendar calendar = Calendar.getInstance();
mSelectedYear = calendar.get(Calendar.YEAR);
mSelectedMonth = calendar.get(Calendar.MONTH);
mSelectedDay = calendar.get(Calendar.DAY_OF_MONTH);
// 初始化视图
initViews();
mPlaybackArea = findViewById(R.id.remote_playback_area);
mControlArea = findViewById(R.id.control_area);
mDisplayLayout = findViewById(R.id.display_layout);
mExitFullScreenButton = findViewById(R.id.exit_btn);
// 设置日期显示模块
setupDatePicker();
// 使用MainActivity已初始化的SDK实例
EZOpenSDK.getInstance().setAccessToken(mAccessToken);
// 默认加载当天的录像
loadVideosForSelectedDate();
setupFullscreenButton();
mTitleBar = findViewById(R.id.title);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT
);
mPlaybackArea.setLayoutParams(params);
}
}
private void initViews() {
// 查找ListView
mListView = findViewById(R.id.listView);
if (mListView == null) {
Log.e(TAG, "ListView not found with ID listView");
return;
}
mNoVideoLayout = findViewById(R.id.novideo_img_cloud);
// 初始化适配器
mAdapter = new PlaybackAdapter();
mListView.setAdapter(mAdapter);
// 设置点击事件
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
VideoInfo video = mVideoList.get(position);
playVideo(video);
}
});
// 获取播放器视图 - 使用SurfaceView
mPlaybackSurfaceView = findViewById(R.id.remote_playback_wnd_sv);
mPauseButton = findViewById(R.id.remote_playback_pause_btn);
mPauseButton.setOnClickListener(v -> togglePlayPause());
if (mPlaybackSurfaceView != null) {
// 设置SurfaceHolder回调
mSurfaceHolder = mPlaybackSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
} else {
Log.e(TAG, "SurfaceView not found with ID remote_playback_wnd_sv");
}
}
private void togglePlayPause() {
if (mEZPlayer == null) return;
if (isVideoPlaying) {
// 当前正在播放 → 暂停
try {
mEZPlayer.pausePlayback();
mPauseButton.setImageResource(R.drawable.play_full_play_sel); // 切换为播放图标
isVideoPlaying = false;
} catch (Exception e) {
Log.e(TAG, "暂停播放失败", e);
}
} else {
// 当前已暂停 → 继续播放
try {
mEZPlayer.resumePlayback();
mPauseButton.setImageResource(R.drawable.play_full_pause_sel); // 切换为暂停图标
isVideoPlaying = true;
} catch (Exception e) {
Log.e(TAG, "继续播放失败", e);
}
}
}
// SurfaceHolder回调方法
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "Surface created");
// 当Surface创建时,如果有播放器实例,设置SurfaceHolder
if (mEZPlayer != null) {
mEZPlayer.setSurfaceHold(holder);
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG, "Surface changed: " + width + "x" + height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Surface destroyed");
// 当Surface销毁时,释放播放器资源
stopPlayback();
}
private void setupDatePicker() {
mDateTextView = findViewById(R.id.date_text);
ImageButton datePickerButton = findViewById(R.id.date_picker_button);
updateDateDisplay();
datePickerButton.setOnClickListener(v -> showDatePickerDialog());
}
private void updateDateDisplay() {
String formattedDate = String.format(Locale.getDefault(),
"%d年%02d月%02d日",
mSelectedYear,
mSelectedMonth + 1, // 月份需要+1
mSelectedDay);
mDateTextView.setText(formattedDate);
}
private void showDatePickerDialog() {
final AlertDialog dlg = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog).create();
dlg.show();
Window window = dlg.getWindow();
window.setContentView(R.layout.datepicker_layout);
// 设置对话框宽度
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
window.setAttributes(lp);
// 获取并初始化 DatePicker
DatePicker dpPicker = window.findViewById(R.id.dpPicker);
// 确保日期选择器正确显示所有组件
ViewGroup rootView = (ViewGroup) dpPicker.getChildAt(0);
if (rootView != null) {
ViewGroup childView = (ViewGroup) rootView.getChildAt(0);
if (childView != null) {
// 确保所有选择器组件可见
for (int i = 0; i < childView.getChildCount(); i++) {
childView.getChildAt(i).setVisibility(View.VISIBLE);
}
}
}
dpPicker.init(mSelectedYear, mSelectedMonth, mSelectedDay, null);
// 设置按钮事件
RelativeLayout yesButton = window.findViewById(R.id.YES);
RelativeLayout noButton = window.findViewById(R.id.NO);
yesButton.setOnClickListener(v -> {
mSelectedYear = dpPicker.getYear();
mSelectedMonth = dpPicker.getMonth();
mSelectedDay = dpPicker.getDayOfMonth();
updateDateDisplay();
dlg.dismiss();
// 加载新选择的日期的录像
loadVideosForSelectedDate();
});
noButton.setOnClickListener(v -> dlg.dismiss());
}
private void extractParametersFromIntent() {
Bundle extras = getIntent().getExtras();
if (extras != null) {
mAppKey = extras.getString(KEY_APPKEY, "");
mDeviceSerial = extras.getString(KEY_SERIAL, "").toUpperCase();
mVerifyCode = extras.getString(KEY_VERIFYCODE, "");
mAccessToken = extras.getString(KEY_ACCESSTOKEN, "");
mCameraNo = extras.getInt(KEY_CAMERANO, 1); // 默认为通道1
Log.d(TAG, "Received parameters:");
Log.d(TAG, "AppKey: " + mAppKey);
Log.d(TAG, "DeviceSerial (Upper): " + mDeviceSerial);
Log.d(TAG, "VerifyCode: " + mVerifyCode);
Log.d(TAG, "AccessToken: " + mAccessToken);
Log.d(TAG, "CameraNo: " + mCameraNo);
} else {
Log.e(TAG, "No parameters received from intent");
}
}
private void loadVideosForSelectedDate() {
if (TextUtils.isEmpty(mDeviceSerial) || TextUtils.isEmpty(mAccessToken)) {
Toast.makeText(this, "设备参数不完整", Toast.LENGTH_SHORT).show();
return;
}
// 计算开始和结束时间戳
Calendar cal = Calendar.getInstance();
cal.set(mSelectedYear, mSelectedMonth, mSelectedDay, 0, 0, 0);
long startTime = cal.getTimeInMillis();
long maxEndTime = System.currentTimeMillis();
cal.set(mSelectedYear, mSelectedMonth, mSelectedDay, 23, 59, 59);
long endTime = Math.min(cal.getTimeInMillis(), maxEndTime);
// 发起网络请求获取录像
fetchVideosByTime(startTime, endTime);
}
private void fetchVideosByTime(long startTime, long endTime) {
if (isFinishing() || isDestroyed()) return;
// 加载开始时隐藏无录像提示
runOnUiThread(() -> {
if (mNoVideoLayout != null) {
mNoVideoLayout.setVisibility(View.GONE);
}
if (!isFinishing()) {
Toast.makeText(FanHui.this, "正在加载可播录像", Toast.LENGTH_SHORT).show();
}
});
final String deviceSerial = mDeviceSerial.toUpperCase();
mExecutorService.execute(() -> {
HttpURLConnection conn = null;
try {
String currentUrl = VIDEO_BY_TIME_URL;
int redirectCount = 0;
final int MAX_REDIRECTS = 3;
while (redirectCount < MAX_REDIRECTS) {
URL url = new URL(currentUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);
conn.setConnectTimeout(15000);
conn.setReadTimeout(30000);
// 构建POST数据
StringBuilder postData = new StringBuilder();
postData.append("accessToken=").append(URLEncoder.encode(mAccessToken, "UTF-8"));
postData.append("&deviceSerial=").append(URLEncoder.encode(deviceSerial, "UTF-8"));
postData.append("&channelNo=").append(mCameraNo);
postData.append("&startTime=").append(startTime);
postData.append("&endTime=").append(endTime);
postData.append("&recType=0"); // 1-云存储录像 0-系统自动选择
postData.append("&version=2.0"); // 分页版本
postData.append("&pageSize=100"); // 分页大小
OutputStream os = conn.getOutputStream();
os.write(postData.toString().getBytes("UTF-8"));
os.flush();
os.close();
int responseCode = conn.getResponseCode();
// 处理重定向
if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP ||
responseCode == HttpURLConnection.HTTP_MOVED_PERM ||
responseCode == HttpURLConnection.HTTP_SEE_OTHER) {
String newUrl = conn.getHeaderField("Location");
if (newUrl == null) break;
currentUrl = newUrl;
redirectCount++;
conn.disconnect();
continue;
}
// 处理正常响应
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); // 明确指定UTF-8编码
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
String responseString = response.toString();
Log.d(TAG, "API响应: " + responseString); // 记录原始响应
// 检查是否是HTML响应
if (responseString.startsWith("<html>") || responseString.startsWith("<!DOCTYPE")) {
Log.e(TAG, "服务器返回HTML: " + responseString);
runOnUiThread(() -> {
if (!isFinishing()) {
Toast.makeText(FanHui.this, "服务器返回错误页面", Toast.LENGTH_LONG).show();
}
});
return;
}
// 解析JSON响应
try {
JSONObject json = new JSONObject(responseString);
String code = json.optString("code", "0");
if ("200".equals(code)) {
Object dataObj = json.opt("data");
List<VideoInfo> videos = new ArrayList<>();
if (dataObj == null) {
// 处理data为null的情况(无录像)
Log.w(TAG, "API返回data为null,表示没有录像");
} else if (dataObj instanceof JSONArray) {
// 处理数组格式响应
JSONArray dataArray = (JSONArray) dataObj;
videos = parseVideoData(dataArray);
} else if (dataObj instanceof JSONObject) {
// 处理分页格式响应
JSONObject dataObject = (JSONObject) dataObj;
// 使用 optJSONArray 安全处理 null 值
JSONArray filesArray = dataObject.optJSONArray("files");
if (filesArray != null) {
videos = parseVideoData(filesArray);
} else {
JSONArray recordsArray = dataObject.optJSONArray("records");
if (recordsArray != null) {
videos = parseVideoData(recordsArray);
} else {
JSONArray listArray = dataObject.optJSONArray("list");
if (listArray != null) {
videos = parseVideoData(listArray);
} else {
Log.w(TAG, "分页结构缺少有效的录像数组字段");
// 尝试从其他可能字段获取
if (dataObject.has("videoList")) {
videos = parseVideoData(dataObject.optJSONArray("videoList"));
} else {
Log.w(TAG, "API 返回的分页结构不包含有效录像数据: " + dataObject.toString());
}
}
}
}
} else {
Log.w(TAG, "未知的data类型: " + dataObj.getClass().getSimpleName());
// 尝试强制转换
try {
videos = parseVideoData(new JSONArray().put(dataObj));
} catch (Exception e) {
Log.e(TAG, "强制转换失败", e);
}
}
// 更新UI显示录像列表
List<VideoInfo> finalVideos = videos;
runOnUiThread(() -> {
mVideoList.clear();
mVideoList.addAll(finalVideos);
mAdapter.notifyDataSetChanged();
// 根据录像数据控制提示布局可见性
if (finalVideos.isEmpty()) {
Toast.makeText(FanHui.this, "该时间段没有录像", Toast.LENGTH_SHORT).show();
if (mNoVideoLayout != null) {
mNoVideoLayout.setVisibility(View.VISIBLE); // 显示无录像提示
}
} else {
if (mNoVideoLayout != null) {
mNoVideoLayout.setVisibility(View.GONE); // 隐藏无录像提示
}
}
});
} else {
String msg = json.optString("msg", "未知错误");
Log.e(TAG, "获取录像失败: " + msg);
runOnUiThread(() -> Toast.makeText(FanHui.this,
"获取录像失败: " + msg, Toast.LENGTH_SHORT).show());
}
} catch (JSONException e) {
Log.e(TAG, "JSON解析错误: " + e.getMessage(), e);
// 添加原始响应内容日志
Log.e(TAG, "原始响应内容: " + responseString);
runOnUiThread(() -> Toast.makeText(FanHui.this,
"数据格式错误: " + e.getMessage(), Toast.LENGTH_LONG).show());
}
break; // 跳出重定向循环
} else {
Log.e(TAG, "HTTP错误: " + responseCode);
if (!isFinishing() && !isDestroyed()) {
runOnUiThread(() -> Toast.makeText(FanHui.this,
"网络请求失败: " + responseCode, Toast.LENGTH_SHORT).show());
}
break;
}
}
if (redirectCount >= MAX_REDIRECTS) {
Log.e(TAG, "重定向次数超过限制");
runOnUiThread(() -> {
if (!isFinishing()) {
Toast.makeText(FanHui.this, "重定向次数过多", Toast.LENGTH_SHORT).show();
}
});
}
} catch (Exception e) {
Log.e(TAG, "获取录像异常", e);
if (!isFinishing() && !isDestroyed()) {
runOnUiThread(() -> Toast.makeText(FanHui.this,
"获取录像出错: " + e.getMessage(), Toast.LENGTH_SHORT).show());
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
});
}
private List<VideoInfo> parseVideoData(JSONArray records) throws JSONException {
List<VideoInfo> videos = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
for (int i = 0; i < records.length(); i++) {
JSONObject record = records.getJSONObject(i);
VideoInfo video = new VideoInfo();
// 兼容多种ID字段名
video.id = record.optString("id",
record.optString("fileId",
record.optString("recordId", "N/A")));
// 兼容多种开始时间字段
video.startTime = record.optLong("startTime",
record.optLong("beginTime",
record.optLong("start_time", 0)));
// 兼容多种结束时间字段
video.endTime = record.optLong("endTime",
record.optLong("stopTime",
record.optLong("end_time", 0)));
// 检查时间有效性
if (video.startTime == 0 || video.endTime == 0) {
Log.w(TAG, "跳过无效时间段: start=" + video.startTime + ", end=" + video.endTime);
continue;
}
// 格式化时间显示
try {
Date startDate = new Date(video.startTime);
Date endDate = new Date(video.endTime);
video.timeRange = sdf.format(startDate) + " - " + sdf.format(endDate);
videos.add(video);
} catch (Exception e) {
Log.e(TAG, "时间格式化失败", e);
}
}
return videos;
}
private void playVideo(VideoInfo video) {
// 停止当前播放
stopPlayback();
// 获取播放地址
playWithTimeRange(video);
}
private void playWithTimeRange(VideoInfo video) {
try {
// 创建播放器实例
mEZPlayer = EZOpenSDK.getInstance().createPlayer(mDeviceSerial, mCameraNo);
// 设置回调处理器
mEZPlayer.setHandler(mHandler);
// 设置验证码
mEZPlayer.setPlayVerifyCode(mVerifyCode);
// 关联播放视图
if (mSurfaceHolder != null) {
mEZPlayer.setSurfaceHold(mSurfaceHolder);
}
// 开始播放云存储录像 - 直接使用时间范围
Calendar startCal = Calendar.getInstance();
startCal.setTimeInMillis(video.startTime);
Calendar endCal = Calendar.getInstance();
endCal.setTimeInMillis(video.endTime);
// 开始播放云存储录像 - 使用Calendar对象
mEZPlayer.startPlayback(startCal, endCal);
Toast.makeText(this, "开始播放录像: " + video.timeRange, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Log.e(TAG, "播放录像失败", e);
Toast.makeText(this, "播放录像失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
isVideoPlaying = true;
runOnUiThread(() -> {
if (mPauseButton != null) {
mPauseButton.setImageResource(R.drawable.play_full_pause_sel);
}
});
}
private void setupFullscreenButton() {
CheckTextButton fullscreenButton = findViewById(R.id.fullscreen_button);
fullscreenButton.setOnClickListener(v -> toggleFullscreen());
// 确保退出按钮在横屏模式下可点击
mExitFullScreenButton.setOnClickListener(v -> toggleFullscreen());
mExitFullScreenButton.setClickable(true); // 明确设置可点击
}
private void toggleFullscreen() {
if (isFullScreen) {
// 退出全屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 恢复UI布局
mControlArea.setVisibility(View.VISIBLE);
mDisplayLayout.setVisibility(View.VISIBLE);
mExitFullScreenButton.setVisibility(View.GONE);
mTitleBar.setVisibility(View.VISIBLE);
// 恢复播放器布局
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
getResources().getDimensionPixelSize(R.dimen.playback_height) // 300dp
);
mPlaybackArea.setLayoutParams(params);
} else {
// 进入全屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 隐藏其他UI元素
mControlArea.setVisibility(View.GONE);
mDisplayLayout.setVisibility(View.GONE);
mExitFullScreenButton.setVisibility(View.VISIBLE);
mTitleBar.setVisibility(View.GONE);
// 设置播放器全屏
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT
);
mPlaybackArea.setLayoutParams(params);
// 强制SurfaceView重绘
if (mSurfaceHolder != null) {
mSurfaceHolder.setFixedSize(getScreenWidth(), getScreenHeight());
}
}
isFullScreen = !isFullScreen;
}
private int getScreenWidth() {
return getResources().getDisplayMetrics().widthPixels;
}
// 获取屏幕高度
private int getScreenHeight() {
return getResources().getDisplayMetrics().heightPixels;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// 横屏时隐藏标题栏
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
mTitleBar.setVisibility(View.GONE);
// 确保播放器填充屏幕
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT
);
mPlaybackArea.setLayoutParams(params);
} else {
mTitleBar.setVisibility(View.VISIBLE);
// 恢复竖屏布局
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
getResources().getDimensionPixelSize(R.dimen.playback_height) // 300dp
);
mPlaybackArea.setLayoutParams(params);
}
// 确保播放器正确重绘
if (mEZPlayer != null && mSurfaceHolder != null) {
mEZPlayer.setSurfaceHold(mSurfaceHolder);
}
}
private void stopPlayback() {
if (mEZPlayer != null) {
try {
mEZPlayer.stopPlayback();
mEZPlayer.release();
} catch (Exception e) {
Log.e(TAG, "停止播放失败", e);
}
mEZPlayer = null;
}
// 重置播放状态
isVideoPlaying = false;
runOnUiThread(() -> {
if (mPauseButton != null) {
mPauseButton.setImageResource(R.drawable.play_full_pause_sel);
}
});
}
// 播放器回调处理器 - 增强回调处理
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_PLAYBACK_PLAY_SUCCESS:
Log.i(TAG, "回放播放成功");
Toast.makeText(FanHui.this, "回放播放成功", Toast.LENGTH_SHORT).show();
isVideoPlaying = true; // 确保状态同步
break;
case MSG_PLAYBACK_PLAY_FAIL:
Log.e(TAG, "回放播放失败");
BaseException error = (BaseException) msg.obj;
int errorCode = error.getErrorCode();
String errorMsg = "播放失败: " + errorCode;
if (errorCode == ErrorCode.ERROR_INNER_VERIFYCODE_NEED) {
errorMsg = "需要验证码";
} else if (errorCode == ErrorCode.ERROR_INNER_VERIFYCODE_ERROR) {
errorMsg = "验证码错误";
} else if (errorCode == ErrorCode.ERROR_TRANSF_ACCESSTOKEN_ERROR) {
errorMsg = "accessToken无效";
} else if (errorCode == ErrorCode.ERROR_TRANSF_DEVICE_OFFLINE) {
errorMsg = "设备离线";
}
Toast.makeText(FanHui.this, errorMsg, Toast.LENGTH_LONG).show();
break;
case MSG_PLAYBACK_STOP:
Log.i(TAG, "回放停止");
isVideoPlaying = false; // 确保状态同步
break;
}
}
};
@Override
protected void onPause() {
super.onPause();
stopPlayback();
shutdownExecutor();
}
@Override
protected void onResume() {
super.onResume();
// 重新创建线程池
if (mExecutorService == null) {
mExecutorService = Executors.newFixedThreadPool(2);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopPlayback();
shutdownExecutor();
}
private void shutdownExecutor() {
if (mExecutorService != null) {
try {
mExecutorService.shutdownNow();
if (!mExecutorService.awaitTermination(1, TimeUnit.SECONDS)) {
Log.w(TAG, "线程池未能在超时时间内关闭");
}
} catch (InterruptedException e) {
Log.e(TAG, "线程池关闭被中断", e);
Thread.currentThread().interrupt();
}
mExecutorService = null;
}
}
// 录像信息数据结构
public static class VideoInfo {
String id;
long startTime;
long endTime;
String timeRange;
}
// 列表适配器
public class PlaybackAdapter extends BaseAdapter {
@Override
public int getCount() {
return mVideoList.size();
}
@Override
public Object getItem(int position) {
return mVideoList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.video_item_layout, parent, false);
holder = new ViewHolder();
holder.timeTextView = convertView.findViewById(R.id.time_text);
holder.durationTextView = convertView.findViewById(R.id.duration_text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
VideoInfo video = mVideoList.get(position);
// 计算持续时间(分钟)
long durationMinutes = (video.endTime - video.startTime) / (1000 * 60);
holder.timeTextView.setText(video.timeRange);
holder.durationTextView.setText(durationMinutes + "分钟");
return convertView;
}
class ViewHolder {
TextView timeTextView;
TextView durationTextView;
}
}
} 用最简单的方式最少的代码修改量实现从MainActivity进入FanHui后,再次返回MainActivity时MainActivity继续播放直播预览视频。
最新发布