apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.hik.netsdk.SimpleDemo"
minSdkVersion 16 // 注意:ExoPlayer 要求最低 SDK 16
targetSdkVersion 28
versionCode 11
versionName "3.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {
abiFilters "armeabi-v7a","arm64-v8a"
}
}
buildTypes {
release {
minifyEnabled false
shrinkResources false // 添加此选项
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
shrinkResources false // 添加此选项
}
}
sourceSets {
main {
res.srcDirs = [
'src/main/res',
'src/main/res/layout/DevMgtUI',
'src/main/res/layout/BusinessUI'
]
jniLibs.srcDirs = ['libs']
}
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
buildToolsVersion = '28.0.3'
}
dependencies {
implementation files('libs/HCNetSDK.jar')
implementation files('libs/PlayerSDK_hcnetsdk.jar')
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:support-v4:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'io.github.ezviz-open:ezviz-sdk:5.20'
compile 'com.ezviz.sdk:ezuikit:2.2.1'
implementation files('libs/PlayerSDK.jar')
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation files('libs/ERTC_Android_SDK_1.5.0.1.aar')
implementation 'com.squareup.okhttp3:okhttp:3.12.1'
implementation 'com.google.code.gson:gson:2.8.5'
implementation files('libs/hikvision-sdk.jar')
// =========== 添加 ExoPlayer 依赖 ===========
implementation 'com.google.android.exoplayer:exoplayer-core:2.18.7'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.18.7'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.18.7'
implementation 'com.google.android.exoplayer:extension-okhttp:2.18.7'
}
package com.hik.netsdk.SimpleDemo.View;
import android.app.AlertDialog;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.gson.Gson;
import com.hik.netsdk.SimpleDemo.R;
import com.hik.netsdk.SimpleDemo.View.DevMgtUI.AddDevActivity;
import com.videogo.errorlayer.ErrorInfo;
import com.videogo.openapi.EZConstants;
import com.videogo.openapi.EZOpenSDK;
import com.videogo.openapi.EZPlayer;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
// 核心参数
private String mDeviceSerial;
private String mVerifyCode;
private int mCameraNo = 1;
private boolean isEzDevice = false;
// 萤石云相关参数
private String mAppKey = "a794d58c13154caeb7d2fbb5c3420c65";
private String mAccessToken = "";
// UI组件
private SurfaceView mPreviewSurface;
private SurfaceHolder mSurfaceHolder;
private Toolbar m_toolbar;
private ProgressBar mProgressBar;
private RelativeLayout mControlLayout;
private ImageButton mRotateButton;
// 播放器相关
private SimpleExoPlayer mExoPlayer;
private String mPlaybackUrl;
private final OkHttpClient mHttpClient = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
// 播放器状态监听器
private final Player.Listener mPlayerListener = new Player.Listener() {
@Override
public void onPlaybackStateChanged(int state) {
switch (state) {
case Player.STATE_READY:
mProgressBar.setVisibility(View.GONE);
Log.d("ExoPlayer", "播放准备就绪");
break;
case Player.STATE_BUFFERING:
mProgressBar.setVisibility(View.VISIBLE);
Log.d("ExoPlayer", "缓冲中...");
break;
case Player.STATE_ENDED:
Log.d("ExoPlayer", "播放结束");
break;
case Player.STATE_IDLE:
Log.d("ExoPlayer", "空闲状态");
break;
}
}
@Override
public void onPlayerError(com.google.android.exoplayer2.PlaybackException error) {
Log.e("ExoPlayer", "播放错误: " + error.getMessage());
handleError("播放错误: " + error.getMessage(), -1);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化UI组件
initUIComponents();
// 获取启动参数
parseIntentParams();
// 初始化SDK
initSDK();
// 参数校验
if (mDeviceSerial == null || mDeviceSerial.isEmpty()) {
showErrorAndFinish("设备序列号不能为空");
return;
}
Log.d("MainActivity", "onCreate完成: isEzDevice=" + isEzDevice);
}
private void initUIComponents() {
// 基础UI
m_toolbar = findViewById(R.id.toolbar);
setSupportActionBar(m_toolbar);
// 预览相关UI
mPreviewSurface = findViewById(R.id.realplay_sv);
mSurfaceHolder = mPreviewSurface.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
mProgressBar = findViewById(R.id.liveProgressBar);
mControlLayout = findViewById(R.id.rl_control);
mRotateButton = findViewById(R.id.ib_rotate2);
// 设置旋转按钮点击事件
mRotateButton.setOnClickListener(v -> changeScreen());
// 隐藏管理UI
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer != null) {
drawer.setVisibility(View.GONE);
}
if (m_toolbar != null) {
m_toolbar.setVisibility(View.GONE);
}
// 初始化控制按钮
initControlButtons();
Log.d("UI", "UI组件初始化完成");
}
private void parseIntentParams() {
// 获取Intent参数
Intent intent = getIntent();
isEzDevice = true; // 只支持萤石设备
mAccessToken = intent.getStringExtra("accessToken");
// 优先使用bundle
Bundle bundle = intent.getExtras();
if (bundle != null) {
mDeviceSerial = bundle.getString("devSn");
mVerifyCode = bundle.getString("verifyCode");
mCameraNo = bundle.getInt("cameraNo", 1);
}
// 兼容直接Extra方式
if (mDeviceSerial == null) mDeviceSerial = intent.getStringExtra("devSn");
if (mVerifyCode == null) mVerifyCode = intent.getStringExtra("verifyCode");
if (mCameraNo == 0) mCameraNo = intent.getIntExtra("cameraNo", 1);
Log.d("Params", "设备序列号: " + mDeviceSerial + ", 通道号: " + mCameraNo);
}
private void initSDK() {
try {
// 使用反射检查初始化状态
boolean isInitialized = false;
try {
// 尝试获取实例
EZOpenSDK instance = EZOpenSDK.getInstance();
if (instance != null) {
isInitialized = true;
}
} catch (Exception e) {
// 捕获未初始化的异常
isInitialized = false;
}
// 未初始化时进行初始化
if (!isInitialized) {
EZOpenSDK.initLib(getApplication(), mAppKey);
Log.d("EZSDK", "萤石SDK初始化完成");
}
// 设置AccessToken
if (mAccessToken != null && !mAccessToken.isEmpty()) {
EZOpenSDK.getInstance().setAccessToken(mAccessToken);
Log.d("EZToken", "AccessToken设置成功");
} else {
Log.w("EZToken", "AccessToken缺失!");
}
} catch (Exception e) {
Log.e("EZSDK", "萤石SDK初始化失败", e);
handleError("萤石SDK初始化失败: " + e.getMessage(), -1);
}
}
private void initControlButtons() {
// 云台控制按钮
findViewById(R.id.ptz_top_btn).setOnClickListener(v -> controlPTZ("UP"));
findViewById(R.id.ptz_bottom_btn).setOnClickListener(v -> controlPTZ("DOWN"));
findViewById(R.id.ptz_left_btn).setOnClickListener(v -> controlPTZ("LEFT"));
findViewById(R.id.ptz_right_btn).setOnClickListener(v -> controlPTZ("RIGHT"));
// 变焦控制
findViewById(R.id.focus_add).setOnClickListener(v -> controlZoom("ZOOM_IN"));
findViewById(R.id.foucus_reduce).setOnClickListener(v -> controlZoom("ZOOM_OUT"));
// 水平布局控制按钮
findViewById(R.id.ptz_top_btn2).setOnClickListener(v -> controlPTZ("UP"));
findViewById(R.id.ptz_bottom_btn2).setOnClickListener(v -> controlPTZ("DOWN"));
findViewById(R.id.ptz_left_btn2).setOnClickListener(v -> controlPTZ("LEFT"));
findViewById(R.id.ptz_right_btn2).setOnClickListener(v -> controlPTZ("RIGHT"));
// 添加萤石云特有功能按钮
// findViewById(R.id.btn_record).setOnClickListener(v -> startLocalRecord());
// findViewById(R.id.btn_stop_record).setOnClickListener(v -> stopLocalRecord());
// findViewById(R.id.btn_capture).setOnClickListener(v -> capturePicture());
Log.d("Controls", "控制按钮初始化完成");
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d("Surface", "Surface created");
// 获取播放地址并开始播放
new Thread(this::fetchPlaybackUrl).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d("Surface", "Surface changed: " + width + "x" + height);
if (mExoPlayer != null) {
mExoPlayer.setVideoSurfaceHolder(holder);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d("Surface", "Surface destroyed");
stopPreview();
}
// ======================== 播放地址获取与播放 ========================
private void fetchPlaybackUrl() {
try {
// 构建请求参数
JSONObject params = new JSONObject();
params.put("accessToken", mAccessToken);
params.put("deviceSerial", mDeviceSerial);
params.put("channelNo", mCameraNo);
params.put("protocol", 2); // 使用HLS协议
if (mVerifyCode != null && !mVerifyCode.isEmpty()) {
params.put("code", mVerifyCode);
}
params.put("expireTime", 7200); // 2小时有效期
// 创建请求体
RequestBody body = RequestBody.create(
MediaType.parse("application/json"),
params.toString()
);
// 创建请求
Request request = new Request.Builder()
.url("https://open.ys7.com/api/lapp/v2/live/address/get")
.post(body)
.build();
// 执行请求
try (Response response = mHttpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
handleError("获取播放地址失败: " + response.code(), response.code());
return;
}
// 解析响应
String responseBody = response.body().string();
JSONObject json = new JSONObject(responseBody);
if ("200".equals(json.getString("code"))) {
JSONObject data = json.getJSONObject("data");
mPlaybackUrl = data.getString("url");
Log.d("PlaybackURL", "获取到播放地址: " + mPlaybackUrl);
// 在主线程初始化播放器
runOnUiThread(this::initExoPlayer);
} else {
handleError("API错误: " + json.getString("msg"), json.getInt("code"));
}
}
} catch (JSONException | IOException e) {
Log.e("FetchURL", "获取播放地址异常", e);
handleError("获取播放地址异常: " + e.getMessage(), -1);
}
}
private void initExoPlayer() {
// 释放现有播放器
if (mExoPlayer != null) {
mExoPlayer.release();
}
// 创建新的播放器实例
mExoPlayer = new SimpleExoPlayer.Builder(this).build();
mExoPlayer.addListener(mPlayerListener);
mExoPlayer.setVideoSurfaceHolder(mSurfaceHolder);
// 创建媒体源
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
MediaSource mediaSource = new HlsMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(Uri.parse(mPlaybackUrl)));
// 准备播放
mExoPlayer.setMediaSource(mediaSource);
mExoPlayer.prepare();
mExoPlayer.setPlayWhenReady(true);
mProgressBar.setVisibility(View.VISIBLE);
Log.d("ExoPlayer", "播放器初始化完成,开始播放");
}
// ======================== 萤石云控制方法 ========================
private int mapDirection(String direction) {
switch (direction) {
// case "UP": return EZCloudControl.DIRECTION_UP;
// case "DOWN": return EZCloudControl.DIRECTION_DOWN;
// case "LEFT": return EZCloudControl.DIRECTION_LEFT;
// case "RIGHT": return EZCloudControl.DIRECTION_RIGHT;
// case "ZOOM_IN": return EZCloudControl.DIRECTION_ZOOM_IN;
// case "ZOOM_OUT": return EZCloudControl.DIRECTION_ZOOM_OUT;
default: return -1;
}
}
// 修改控制方法
private void controlPTZ(String direction) {
int directionCode = mapDirection(direction);
if (directionCode == -1) return;
// 开始控制
// EZCloudControl.startPTZControl(
// mAccessToken,
// mDeviceSerial,
// mCameraNo,
// directionCode,
// EZCloudControl.SPEED_MEDIUM, // 中等速度
// new EZCloudControl.ControlCallback() {
// @Override
// public void onSuccess(String response) {
// Log.d("PTZControl", "开始云台控制成功: " + direction);
//
// // 300ms后停止控制
// new Handler(Looper.getMainLooper()).postDelayed(() -> {
// stopPTZControl(directionCode);
// }, 300);
// }
//
// @Override
// public void onFailure(String error) {
// Log.e("PTZControl", "开始云台控制失败: " + error);
// handleError("云台控制失败: " + error, -1);
// }
// });
}
// private void stopPTZControl(int directionCode) {
// EZCloudControl.stopPTZControl(
// mAccessToken,
// mDeviceSerial,
// mCameraNo,
// directionCode,
// new EZCloudControl.ControlCallback() {
// @Override
// public void onSuccess(String response) {
// Log.d("PTZControl", "停止云台控制成功");
// }
//
// @Override
// public void onFailure(String error) {
// Log.e("PTZControl", "停止云台控制失败: " + error);
// // 可选择重试或其他处理
// }
// });
// }
private void controlZoom(String command) {
String direction = "ZOOM_IN".equals(command) ? "ZOOM_IN" : "ZOOM_OUT";
controlPTZ(direction);
}
// ======================== 萤石云扩展功能 ========================
// 开始本地录像
private void startLocalRecord() {
// 这里需要根据实际需求实现
Toast.makeText(this, "开始录像", Toast.LENGTH_SHORT).show();
}
// 停止本地录像
private void stopLocalRecord() {
// 这里需要根据实际需求实现
Toast.makeText(this, "停止录像", Toast.LENGTH_SHORT).show();
}
// 截图
private void capturePicture() {
// 这里需要根据实际需求实现
Toast.makeText(this, "截图已保存", Toast.LENGTH_SHORT).show();
}
// ======================== 通用功能方法 ========================
public void changeScreen(View view) {
changeScreen();
}
private void changeScreen() {
if (mControlLayout.getVisibility() == View.VISIBLE) {
mControlLayout.setVisibility(View.GONE);
} else {
mControlLayout.setVisibility(View.VISIBLE);
}
}
private void handleError(String message, int errorCode) {
String fullMessage = message + " (错误码: " + errorCode + ")";
// 萤石云特有错误码处理
switch (errorCode) {
case 400001:
fullMessage = "AccessToken无效";
break;
case 400002:
fullMessage = "设备不存在";
break;
case 400007:
fullMessage = "设备不在线";
break;
case 400034:
fullMessage = "验证码错误";
break;
case 400035:
fullMessage = "设备已被自己添加";
break;
case 400036:
fullMessage = "设备已被别人添加";
break;
default:
fullMessage = "萤石云错误: " + errorCode;
}
new AlertDialog.Builder(this)
.setTitle("预览失败")
.setMessage(fullMessage)
.setPositiveButton("确定", (d, w) -> finish())
.setCancelable(false)
.show();
}
private void showErrorAndFinish(String message) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
new Handler().postDelayed(this::finish, 3000);
}
private void stopPreview() {
if (mExoPlayer != null) {
mExoPlayer.release();
mExoPlayer = null;
Log.d("Preview", "预览已停止");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopPreview();
Log.d("Lifecycle", "onDestroy");
}
@Override
protected void onPause() {
super.onPause();
if (mExoPlayer != null) {
mExoPlayer.setPlayWhenReady(false);
Log.d("Lifecycle", "暂停播放");
}
}
@Override
protected void onResume() {
super.onResume();
if (mExoPlayer != null) {
mExoPlayer.setPlayWhenReady(true);
Log.d("Lifecycle", "恢复播放");
}
}
@Override
public void onBackPressed() {
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer != null && drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main_opt, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_1) {
startActivity(new Intent(this, AddDevActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}
依据上述代码解决下面这个报错:Dependencies using groupId `com.android.support` and `androidx.*` can not be combined but found `com.android.support:design:28.0.0` and `androidx.media:media:1.6.0` incompatible dependencies
最新发布