原文地址: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);
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("停止播放");
}
}
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;
}
}
}
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
动漫制作技巧是很多新人想了解的问题,今天小编就来解答与大家分享一下动漫制作流程,为了帮助有兴趣的同学理解,大多数人会选择动漫培训机构,那么今天小编就带大家来看看动漫制作要掌握哪些技巧?一、动漫作品首先完成草图设计和原型制作。设计草图要有目的、有对象、有步骤、要形象、要简单、符合实际。设计图要一致性,以保证制作的顺利进行。二、原型制作是根据设计图纸和制作材料,可以是手绘也可以是3d软件创建。在此步骤中,要注意的问题是色彩和平面布局。三、动漫制作制作完成后,加工成型。完成不同的表现形式后,就要对设计稿进行加工处理,使加工的难易度降低,并得到一些基本准确的概念,以便于后续的大样、准确的尺寸制定。四、
2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p