原文地址:Android開發 海康視訊 多路視訊播放 | Stars-One的雜貨小窩
最近公司有個專案需要對接到海康監控攝像頭來實現對應的實時播放和回放,但這兩個不是我們今天要討論的重點,APP首頁,需要實現同時播放兩個視訊,全網蒐集了下,都沒有找到相關資源,於是便是自己研究,最終也是成功實現了功能
注:本文是基於海康視訊SDK的demo專案進行功能的增加,預設各位研究閱讀了海康SDK文件及已成功執行demo程式的前提下
效果圖
首先放下效果圖吧
上面的右邊即是同時播放了兩個視訊,兩個視訊都是一個Fragment,然後各自放在了一個FrameLayout裡面
實現
海康裝置官方的demo中,是使用了Activity來實現視訊播放的功能,但是由於我們這邊需要播放多個,頁面可以複用一個,只是傳的相關引數不同,所以,需要先稍微改造一下官方的那個Activity的demo,改為Fragment
程式碼比較簡單,都是基於官方的demo改了下,相信各位應該可以看懂
佈局裡只有個SurfaceView,然後需要配置下
Fragment需要在onViewCreated()
方法裡設定SurfaceView
的配置選項
Fragment提供了個startVideo()
的方法,需要傳遞對應的裝置引數進來即可實現播放
startVideo()
這裡的引數實體類是我自己定義的,各位可以看著改動下,具體是在initVideoSdk()
方法
中進行取值
佈局程式碼:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#021132"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:visibility="gone"
android:id="@+id/svVideo"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
注:原始碼裡程式碼直接拷貝無法直接使用,需要各位看下然後稍微改動下,使用了EventBus,不需要的可以刪除,然後就是視訊播放的引數調整應該就沒有啥問題了
使用
我這裡是使用了兩個FrameLayout,將Fragment設定了進去,即首頁的右下角,兩個FrameLayout是平分了width,我這裡是直接使用了ConstraintLayout約束佈局加輔助線來實現,程式碼如下所示
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/videoView"
android:visibility="invisible"
android:orientation="horizontal"
android:layout_width="200dp"
android:layout_height="200dp">
<FrameLayout
android:id="@+id/fl1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/gl"
android:layout_width="0dp"
android:layout_height="match_parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/gl"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintGuide_percent="0.5"/>
<FrameLayout
android:id="@+id/fl2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/gl"
android:layout_width="0dp"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
使用的話,只需要建立new一個Fragment,之後將其新增到FrameLayout中
//初始化兩個fragment
for (int i = 0; i < 2; i++) {
VideoPreviewFragment videoPreviewFragment = new VideoPreviewFragment();
videoPreviewFragments.add(videoPreviewFragment);
if (i == 0) {
FragmentUtils.add(getSupportFragmentManager(), videoPreviewFragment, R.id.fl1, "fragment" + i);
} else {
FragmentUtils.add(getSupportFragmentManager(), videoPreviewFragment, R.id.fl2, "fragment" + i);
}
}
然後再適應的時機,呼叫startVideo()
,傳入對應的引數即可
Fragment的使用說明可以參考這一篇Android開發——Fragment的簡單使用總結 - Stars-one - 部落格園,這裡不再過多贅述
首頁的補充說明
首頁其實底下是個WebView,然後右下角的是固定懸浮在上面的,由H5那邊進行計算,將對應的座標和長寬傳了過來,由APP這邊去設定View的寬高
設定View的寬高和大小(是以單位px):
private void setMargins(View v, int l, int t, int width, int height) {
if (v.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
p.setMargins(l, t, 0, 0);
p.height = height;
p.width = width;
v.requestLayout();
}
}
使用的時候,需要改變View的顯示和隱藏,如下程式碼
//要先隱藏,更改尺寸,再顯示,更改尺寸才起作用
videoView.setVisibility(View.GONE);
setMargins(videoView, event.getLeft(), event.getTop(), event.getWidth(), event.getHeight());
videoView.setVisibility(View.VISIBLE);
原始碼
VideoPreviewFragment原始碼
package com.tyky.monitorboard.activity;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import com.blankj.utilcode.util.ToastUtils;
import com.hikvision.netsdk.NET_DVR_PREVIEWINFO;
import com.socks.library.KLog;
import com.tyky.monitorboard.R;
import com.tyky.monitorboard.control.DevManageGuider;
import com.tyky.monitorboard.control.SDKGuider;
import com.tyky.monitorboard.event.ShowVideoViewEvent;
import com.tyky.monitorboard.model.BaseVideoChannel;
import com.tyky.monitorboard.utils.HkVideoHelper;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
/**
* 視訊實時預覽
*/
public class VideoPreviewFragment extends Fragment {
private SurfaceView surfaceView;
private int m_iPreviewHandle = -1; // playback
private int m_iSelectChannel = 1;
//0 main_stream 1 sub_stream 2 third_stream
private int m_iSelectStreamType = 0;
private int m_iUserID = -1; // return by NET_DVR_Login_v30
private BaseVideoChannel baseVideoChannel;
private boolean isDeviceLogin;
public VideoPreviewFragment() {
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
surfaceView = view.findViewById(R.id.svVideo);
configSurface();
}
public void startVideo(BaseVideoChannel baseVideoChannel) {
if (this.baseVideoChannel != null) {
return;
}
this.baseVideoChannel = baseVideoChannel;
initVideoSdk();
//自動開始播放
new Thread(() -> {
try {
//稍微等待5s 視訊播放的資源初始化
Thread.sleep(2*1000);
videoPlay();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_video_preview, container, false);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventBus.getDefault().register(this);
}
private void initVideoSdk() {
//String deviceName = "公司的";
//String ip = "192.9.11.72";
//int port = 8000;
//String userName = "admin";
//String pwd = "tyky_1234";
//m_iSelectChannel = 1;
//m_iSelectStreamType = 1;
String deviceName = baseVideoChannel.getName();
String ip = baseVideoChannel.getIpAddress();
int port = Integer.parseInt(baseVideoChannel.getPort());
String userName = baseVideoChannel.getUserName();
String pwd = baseVideoChannel.getPwd();
m_iSelectChannel = baseVideoChannel.getChannel();
m_iSelectStreamType = Integer.valueOf(baseVideoChannel.getStream());
//裝置登入
isDeviceLogin = HkVideoHelper.deviceLogin(deviceName, ip, port, userName, pwd, true);
}
/**
* 初始化surface
*/
private void configSurface() {
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
surfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
if (-1 == m_iPreviewHandle) {
return;
}
Surface surface = surfaceHolder.getSurface();
if (surface.isValid()) {
if (-1 == SDKGuider.g_sdkGuider.m_comPreviewGuider.RealPlaySurfaceChanged_jni(m_iPreviewHandle, 0, surfaceHolder)) {
ToastUtils.showShort("NET_DVR_PlayBackSurfaceChanged" + SDKGuider.g_sdkGuider.GetLastError_jni());
}
}
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
if (-1 == m_iPreviewHandle) {
return;
}
if (surfaceHolder.getSurface().isValid()) {
if (-1 == SDKGuider.g_sdkGuider.m_comPreviewGuider.RealPlaySurfaceChanged_jni(m_iPreviewHandle, 0, null)) {
ToastUtils.showShort("NET_DVR_RealPlaySurfaceChanged" + SDKGuider.g_sdkGuider.GetLastError_jni());
}
}
}
});
surfaceView.setZOrderOnTop(true);
}
@Override
public void onDestroy() {
videoStop();
EventBus.getDefault().unregister(this);
super.onDestroy();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void showVideoView(ShowVideoViewEvent event){
int type = event.getType();
if (type == 1) {
//顯示
surfaceView.setVisibility(View.VISIBLE);
}
if (type==0) {
//隱藏
surfaceView.setVisibility(View.INVISIBLE);
}
}
/**
* 開始播放
*/
private void videoPlay() {
if (!isDeviceLogin) {
ToastUtils.showShort("視訊裝置連線失敗,請檢查視訊裝置配置!");
return;
}
KLog.e("--test","視訊開始播放");
//當前已連線的裝置
ArrayList<DevManageGuider.DeviceItem> devList = SDKGuider.g_sdkGuider.m_comDMGuider.getDevList();
if (devList.size() > 0) {
DevManageGuider.DeviceItem deviceInfo = devList.get(0);
m_iUserID = deviceInfo.m_lUserID;
}
if (m_iPreviewHandle != -1) {
SDKGuider.g_sdkGuider.m_comPreviewGuider.RealPlay_Stop_jni(m_iPreviewHandle);
}
NET_DVR_PREVIEWINFO struPlayInfo = new NET_DVR_PREVIEWINFO();
struPlayInfo.lChannel = m_iSelectChannel;
struPlayInfo.dwStreamType = m_iSelectStreamType;
//bBlocked 0:非阻塞取流 1:阻塞取流
struPlayInfo.bBlocked = 1;
struPlayInfo.hHwnd = surfaceView.getHolder();
m_iPreviewHandle = SDKGuider.g_sdkGuider.m_comPreviewGuider.RealPlay_V40_jni(m_iUserID, struPlayInfo, null);
if (m_iPreviewHandle < 0) {
ToastUtils.showShort("播放失敗,原因::" + SDKGuider.g_sdkGuider.GetLastError_jni());
return;
}
ToastUtils.showShort("開始播放");
}
/**
* 停止播放
*/
private void videoStop() {
if (!SDKGuider.g_sdkGuider.m_comPreviewGuider.RealPlay_Stop_jni(m_iPreviewHandle)) {
ToastUtils.showShort("NET_DVR_StopRealPlay m_iPreviewHandle:" + m_iPreviewHandle
+ " error:" + SDKGuider.g_sdkGuider.GetLastError_jni());
return;
}
m_iPreviewHandle = -1;
ToastUtils.showShort("停止播放");
}
}
HkVideoHelper
public class HkVideoHelper {
/**
* 新增視訊裝置
*
* @param isInsertInDb 是否將資料插入資料庫
*/
public static boolean deviceLogin(String devName, String ip, int port, String userName, String pwd, boolean isInsertInDb) {
DevManageGuider.DeviceItem deviceItem = SDKGuider.g_sdkGuider.m_comDMGuider.new DeviceItem();
deviceItem.m_szDevName = devName;
deviceItem.m_struNetInfo = SDKGuider.g_sdkGuider.m_comDMGuider.new DevNetInfo(
ip, port + "", userName, pwd);
if (deviceItem.m_szDevName.isEmpty()) {
deviceItem.m_szDevName = deviceItem.m_struNetInfo.m_szIp;
}
if (SDKGuider.g_sdkGuider.m_comDMGuider.login_v40_jna(deviceItem.m_szDevName, deviceItem.m_struNetInfo)) {
KLog.d("--HkVideoHelper", "裝置連線成功");
return true;
} else {
KLog.d("--HkVideoHelper", "失敗");
return false;
}
}
}