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.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.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.videogo.exception.BaseException;
import com.videogo.exception.ErrorCode;
import com.videogo.openapi.EZConstants;
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;
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 = "serial";
private static final String KEY_VERIFYCODE = "VerifyCode";
private static final String KEY_ACCESSTOKEN = "accessToken";
private static final String KEY_CAMERANO = "cameraNo";
// 回放录像相关
private static final String VIDEO_BY_TIME_URL = "https://open.ys7.com/api/lapp/video/by/time";
private ExecutorService mExecutorService;
private ListView mListView;
private PlaybackAdapter mAdapter;
private List<VideoInfo> mVideoList = new ArrayList<>();
// 播放器相关
private EZPlayer mEZPlayer;
private SurfaceView mPlaybackSurfaceView;
private SurfaceHolder mSurfaceHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ez_playback_list_page);
// 创建线程池
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();
// 设置日期显示模块
setupDatePicker();
// 默认加载当天的录像
loadVideosForSelectedDate();
// 初始化SDK
initSDK();
}
private void initSDK() {
try {
// 初始化萤石云SDK
EZOpenSDK.initLib(getApplication(), mAppKey);
EZOpenSDK.getInstance().setAccessToken(mAccessToken);
} catch (Exception e) {
Log.e(TAG, "SDK初始化失败", e);
Toast.makeText(this, "SDK初始化失败", Toast.LENGTH_SHORT).show();
}
}
private void initViews() {
// 查找ListView
mListView = findViewById(R.id.listView);
if (mListView == null) {
Log.e(TAG, "ListView not found with ID listView");
return;
}
// 初始化适配器
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);
if (mPlaybackSurfaceView != null) {
// 设置SurfaceHolder回调
mSurfaceHolder = mPlaybackSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
} else {
Log.e(TAG, "SurfaceView not found with ID remote_playback_wnd_sv");
}
}
// 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) {
childView.getChildAt(2).setVisibility(View.VISIBLE);
// 确保月选择器可见
childView.getChildAt(1).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, "");
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");
}
}
private void loadVideosForSelectedDate() {
// 计算开始和结束时间戳
Calendar cal = Calendar.getInstance();
cal.set(mSelectedYear, mSelectedMonth, mSelectedDay, 0, 0, 0);
long startTime = cal.getTimeInMillis();
cal.set(mSelectedYear, mSelectedMonth, mSelectedDay, 23, 59, 59);
long endTime = cal.getTimeInMillis();
// 发起网络请求获取录像
fetchVideosByTime(startTime, endTime);
}
private void fetchVideosByTime(long startTime, long endTime) {
mExecutorService.execute(() -> {
try {
URL url = new URL(VIDEO_BY_TIME_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
// 构建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);
postData.append("&startTime=").append(startTime);
postData.append("&endTime=").append(endTime);
postData.append("&recType=").append(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_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)) {
JSONArray data = json.getJSONArray("data");
List<VideoInfo> videos = parseVideoData(data);
runOnUiThread(() -> {
mVideoList.clear();
mVideoList.addAll(videos);
mAdapter.notifyDataSetChanged();
});
} else {
String msg = json.optString("msg", "未知错误");
Log.e(TAG, "获取录像失败: " + msg);
runOnUiThread(() -> Toast.makeText(FanHui.this,
"获取录像失败: " + msg, Toast.LENGTH_SHORT).show());
}
} else {
Log.e(TAG, "HTTP错误: " + responseCode);
runOnUiThread(() -> Toast.makeText(FanHui.this,
"网络请求失败: " + responseCode, Toast.LENGTH_SHORT).show());
}
conn.disconnect();
} catch (Exception e) {
Log.e(TAG, "获取录像异常", e);
runOnUiThread(() -> Toast.makeText(FanHui.this,
"获取录像出错: " + e.getMessage(), Toast.LENGTH_SHORT).show());
}
});
}
private List<VideoInfo> parseVideoData(JSONArray data) throws JSONException {
List<VideoInfo> videos = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
for (int i = 0; i < data.length(); i++) {
JSONObject videoObj = data.getJSONObject(i);
VideoInfo video = new VideoInfo();
video.id = videoObj.optString("id");
video.startTime = videoObj.optLong("startTime");
video.endTime = videoObj.optLong("endTime");
video.recType = videoObj.optInt("recType");
// 格式化时间显示
Date startDate = new Date(video.startTime);
Date endDate = new Date(video.endTime);
video.timeRange = sdf.format(startDate) + " - " + sdf.format(endDate);
videos.add(video);
}
return videos;
}
private void playVideo(VideoInfo video) {
// 停止当前播放
stopPlayback();
try {
// 创建播放器实例
mEZPlayer = EZOpenSDK.getInstance().createPlayer(mDeviceSerial, mCameraNo);
// 配置播放器 - 设置回调处理器
mEZPlayer.setHandler(mHandler);
// 设置验证码
mEZPlayer.setPlayVerifyCode(mVerifyCode);
// 关联播放视图 - 使用SurfaceHolder(修复方法名)
if (mSurfaceHolder != null) {
mEZPlayer.setSurfaceHold(mSurfaceHolder);
} else {
Log.e(TAG, "无法关联播放视图");
Toast.makeText(this, "播放视图未初始化", Toast.LENGTH_SHORT).show();
}
// 创建Calendar对象作为参数
Calendar startCal = Calendar.getInstance();
startCal.setTimeInMillis(video.startTime);
Calendar endCal = Calendar.getInstance();
endCal.setTimeInMillis(video.endTime);
// 开始回放
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();
}
}
private void stopPlayback() {
if (mEZPlayer != null) {
mEZPlayer.stopPlayback();
mEZPlayer.release();
mEZPlayer = null;
}
}
// 播放器回调处理器
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EZConstants.EZRealPlayConstants.MSG_REALPLAY_PLAY_SUCCESS:
Log.i(TAG, "回放播放成功");
break;
case EZConstants.EZRealPlayConstants.MSG_REALPLAY_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无效";
}
Toast.makeText(FanHui.this, errorMsg, Toast.LENGTH_LONG).show();
break;
}
}
};
@Override
protected void onPause() {
super.onPause();
if (mEZPlayer != null) {
mEZPlayer.stopRealPlay();
mEZPlayer.release();
mEZPlayer = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopPlayback();
if (mExecutorService != null) {
mExecutorService.shutdown();
}
}
// 录像信息数据结构
private static class VideoInfo {
String id;
long startTime;
long endTime;
int recType;
String timeRange;
}
// 列表适配器
private 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;
}
}
}
开发文档:OPEN API/云直播/播放地址/播放地址接口(新)/获取播放地址
获取播放地址
接口功能:
该接口用于通过设备序列号、通道号获取单台设备的播放地址信息,无法获取永久有效期播放地址。
请求地址
https://open.ys7.com/api/lapp/v2/live/address/get
子账户token请求所需最小权限
"Permission":"Get" "Resource":"dev:序列号"
请求方式
POST
请求参数
参数名 类型 描述 是否必选
accessToken String 授权过程获取的access_token Y
deviceSerial String 设备序列号例如427734222,均采用英文符号,限制最多50个字符 Y
channelNo Integer 通道号,非必选,默认为1 N
protocol Integer 流播放协议,1-ezopen、2-hls、3-rtmp、4-flv,默认为1 N
code String ezopen协议地址的设备的视频加密密码 N
expireTime Integer 过期时长,单位秒;针对hls/rtmp/flv设置有效期,相对时间;30秒-720天 N
type String 地址的类型,1-预览,2-本地录像回放,3-云存储录像回放,非必选,默认为1;回放仅支持rtmp、ezopen、flv协议 N
quality Integer 视频清晰度,1-高清(主码流)、2-流畅(子码流) N
startTime String 本地录像/云存储录像回放开始时间,云存储开始结束时间必须在同一天,示例:2019-12-01 00:00:00 N
stopTime String 本地录像/云存储录像回放结束时间,云存储开始结束时间必须在同一天,示例:2019-12-01 23:59:59 N
supportH265 Integer 请判断播放端是否要求播放视频为H265编码格式,1表示需要,0表示不要求 N
playbackSpeed String 回放倍速。倍速为 -1( 支持的最大倍速)、0.5、1、2、4、8、16;
仅支持protocol为4-flv
且
type为2-本地录像回放( 部分设备可能不支持16倍速) 或者 3-云存储录像回放 N
gbchannel String 国标设备的通道编号,视频通道编号ID N
HTTP请求报文
POST /api/lapp/v2/live/address/get HTTP/1.1
Host: open.ys7.com
Content-Type: application/x-www-form-urlencoded
accessToken=at.dunwhxt2azk02hcn7phqygsybbw0wv6p&deviceSerial=C78957921&channelNo=1
返回数据
{
"msg": "Operation succeeded",
"code": "200",
"data": {
"id": "254708522214232064",
"url": "https://open.ys7.com/v3/openlive/C78957921_1_1.m3u8?expire=1606999273&id=254708522214232064&t=093e5c6668d981e0f0b8d2593d69bdc98060407d1b2f42eaaa17a62b15ee4f99&ev=100",
"expireTime": "2020-12-03 20:41:13"
}
}
返回字段:
字段名 类型 描述
code String 状态码,参考下方返回码。优先判断该错误码,返回200即表示成功。
msg String 状态描述
id String 状态描述
url String 直播地址
expireTime long 直播地址有效期。expireTime参数为空时该字段无效
注意:该接口请求时先解析code属性,如果返回200即表示成功,可继续解析data属性的内容,每一个地址对象中先解析ret属性,如果返回200表示成功,再根据status属性和exception属性判断是否存在异常。
返回码
返回码 返回消息 备注
200 操作成功,获取指定有效期的直播地址 请求成功
201 Created
401 Unauthorized
403 Forbidden
404 Not Found
403 用户不存在 。依据上述开发文档在FanHui.java里添加获取播放地址的功能来实现通过回访地址回放视频。
最新发布