原文地址: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;
}
}
}

Android开发 海康威视 多路视频播放(同时播放视频)的更多相关文章

  1. 【Android开发VR实战】二.播放360&#176;全景视频

    转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53924006 本文出自[DylanAndroid的博客] [Android开发 ...

  2. Android开发教程AnimationDrawable逐帧播放动画

    下面我们一起来看篇Android开发AnimationDrawable控制逐帧播放动画实现过程,希望文章对各位朋友带不一些帮助. 当我们点击按钮时,该图片会不停的旋转,当再次点击按钮时,会停止在当前的 ...

  3. android中使用surfaceview+MediaPlayer播放视频

    Android中播放视频主要有两种方式: 使用其自带的播放器.指定Action为ACTION_VIEW,Data为Uri,Type为其MIME类型 使用android自带的VideoView,这种方法 ...

  4. Android开发手记(14) 使用MediaPlayer播放mp3

    1.获取MediaPlayer实例 (1)可以直接通过new或者create方式: 调用setDataSource和create的区别是,create时已经执行了MediaPlayer.prepare ...

  5. android中使用MediaPlayer和SurfaceView播放视频

    package com.test.video; import java.io.IOException; import android.media.AudioManager; import androi ...

  6. android开发(44) 使用了 SoundPool 播放提示音

    SoundPool 一个声音播放的辅助类,从名字可以看出,它具有 “池”的能力,它先加载声音文件到内存,以支持多次播放声音文件. 特点 SoundPool适合 短小的 声音文件 SoundPool适合 ...

  7. Android开发实战之简单音乐播放器

    最近开始学习音频相关.所以,很想自己做一个音乐播放器,于是,花了一天学习,将播放器的基本功能实现了出来.我觉得学习知识点还是蛮多的,所以写篇博客总结一下关于一个音乐播放器实现的逻辑.希望这篇博文对你的 ...

  8. 【Android 多媒体应用】使用 VideoView 播放视频

    1.MainActivity.java import android.os.Bundle; import android.support.v7.app.AppCompatActivity; impor ...

  9. 【Android 多媒体应用】使用 MediaPlayer 播放视频

    1.MainActivity.java import android.media.AudioManager; import android.media.MediaPlayer; import andr ...

随机推荐

  1. NOAA数据下载方法

    NOAA OneStop https://data.noaa.gov/onestop/about NOAA 数据搜索平台,在一个地方同时搜索NOAA的 Geophysical, oceans, coa ...

  2. JetBrains又出神器啦!Fleet,体验飞一般的感觉

    目录 简介 从eclipse到Fleet Fleet的特性 JetBrains Space 总结 简介 java开发的同学可能对于JetBrains这家公司并不陌生,因为JetBrains号称拥有世界 ...

  3. 如何获得Spring容器里管理的Bean,。不论是Service层,还是实体Dao层

    如何获得Spring容器里管理的Bean,.不论是Service层,还是实体Dao层, 下面的这个必须配置,否则必出错,空指针 下面的这个是代码 而获得bean代码如下: serviceManager ...

  4. 【LeetCode】1432. 改变一个整数能得到的最大差值 Max Difference You Can Get From Changing an Integer

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 暴力 日期 题目地址:https://leetcode ...

  5. 【LeetCode】237. Delete Node in a Linked List 解题报告 (Java&Python&C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 设置当前节点的值为下一个 日期 [LeetCode] ...

  6. 【LeetCode】723. Candy Crush 解题报告 (C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 暴力 日期 题目地址:https://leetcode ...

  7. 【LeetCode】638. Shopping Offers 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 DFS 回溯法 日期 题目地址:https://le ...

  8. 【LeetCode】865. Smallest Subtree with all the Deepest Nodes 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  9. 【剑指Offer】和为S的连续正数序列 解题报告(Python)

    [剑指Offer]和为S的连续正数序列 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-interview ...

  10. Mobile phones(poj1195)

    Mobile phones Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 18453   Accepted: 8542 De ...