Android開發 海康威視 多路視頻播放(同時播放視頻)

原文地址: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;
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章