海康 Hik Web 无插件开发包实现视频直播、回放、快进、下载等

411 阅读9分钟

1、下载依赖

相关依赖包,可从 “海康硬件开发平台” 下载

image.png

  • jquery-1.7.1.min.js
  • AES.js
  • cryptico.min.js
  • crypto-3.1.2.min.js
  • webVideoCtrl.js

2、类型说明

可根据 “类” 的具体功能,进行类型的扩展。

// 配置:插件
export type PluginOptions = {
    szWidth?: number | string; // 播放容器的宽度 =>(单位为”px”, 100%表示撑满插件容器)
    szHight?: number | string; // 播放容器的高度 =>(单位为”px”, 100%表示撑满插件容器)
    bWndFull?: boolean; // 是否支持单窗口双击全屏 => 默认 true
    iPackageType?: 2 | 11; // 数据包类型 => 默认 2(2:PS 格式、11:MP4 格式)
    iWndowType?: 1 | 2 | 3 | 4; // 分屏类型 => 默认 1(1:1*1、2:2*2、3:3*3、4:4*4)
    cbSelWnd?: (xmlDoc: any) => void; // 窗口选中回调事件函数
    cbDoubleClickWnd?: (iWndIndex: number, bFullScreen: boolean) => void; // 窗口双击回调事件函数
    cbEvent?: (iEventType: number, iParam1: any, iParam2: any) => void; // 插件事件回调函数
    cbRemoteConfig?: (xmlDoc: any) => void; // 远程配置库关闭回调
    cbInitPluginComplete?: () => void; // 插件初始化完成回调
};

// 配置:登录
export type LoginOptions = {
    szIP: string; // 设备的 IP 地址
    szPrototocol?: number; // 协议类型 => 默认 1(1:http、2:https)
    szPort: number | string; // 端口号
    szUsername: string; // 用户名
    szPassword: string; // 密码
    async?: boolean; // 是否异步 => true 异步,false 同步
    cgl?: number; // CGI 协议选择 => 默认 自动(1:ISAPI、2:PSIA)
    success?: (xmlDoc: any) => void; // 成功回调
    error?: (xmlDoc: any) => void; // 失败回调
};

// 播放:直播
export type StartRealPlayOptions = {
    iWndIndex?: number; // 窗口索引号 => 默认 0
    iStreamType?: number; // 码流类型 => 默认 1(1:主码流、2:子码流)
    iChannelID?: number; // 设备通道号 => 默认 1
    bZeroChannel?: boolean; // 是否是零通道 => 默认 false
    iPort?: number; // RTSP 端口号 => 如果不传,会自动判断设备 RTSP 端口
    success?: (xmlDoc: any) => void; // 成功回调
    error?: (err: any) => void; // 失败回调
};

// 播放:停止
export type StopRealPlayOptions = {
    iWndIndex?: number; // 窗口索引号 => 不传,表示操作当前选中窗口
    success?: (xmlDoc: any) => void; // 成功回调
    error?: (err: any) => void; // 失败回调
};

// 回放:开始
export type StartPlayBackOptions = {
    iWndIndex?: number; // 窗口索引号 => 默认 0
    szStartTime?: string; // 开始时间 => 默认为当天 00:00:00,格式如:2013-12-23 00:00:00
    szEndTime?: string; // 结束时间 => 默认为当天 23:59:59,格式如:2013-12-23 23:59:59
    iChannelID?: number; // 设备通道号 => 默认 1
    iPort?: number; // RTSP 端口号 => 如果不传,会自动判断设备 RTSP 端口
    iStreamType?: number; // 码流类型 => 默认 1(1:主码流、2:子码流)
    oTransCodeParam?: {}; // 转码回放参数对象,传入此参数,将按照此对象中的编码参数进行转码回放(转码回放需要设备支持,如果不支持,则不要传入这个参数)。
    success?: (xmlDoc: any) => void; // 成功回调
    error?: (err: any) => void; // 失败回调
};

// 下载录像(按时间)
export type DownloadRecordOptions = {
    szPlaybackURI: string; // 回放地址 => 录像 URL,这个 URL 在录像搜索中可以得到
    szFileName: string; // 下载的文件名
    szStartTime: string; // 开始时间
    szEndTime: string; // 结束时间
    bDateDir?: true; // 是否创建日期文件夹(true:创建,false:不创建),默认 true
};

// 搜索录像
export type SearchRecordOptions = {
    iChannelID?: number; // 设备通道号 => 默认 1
    szStartTime?: string; // 开始时间 => 默认为当天 00:00:00,格式如:2013-12-23 00:00:00
    szEndTime?: string; // 结束时间 => 默认为当天 23:59:59,格式如:2013-12-23 23:59:59
    async?: boolean; // 是否异步 => true 异步,false 同步
    iStreamType?: number; // 码流类型 => 默认 1(1:主码流、2:子码流)
    iSearchPos?: number; // 搜索录像的位置(默认为 0),0 表示返回结果的第 0-40 条,40 表示 40-80 条,依次类推
    success?: (xmlDoc: any) => void; // 成功回调
    error?: (status: any, xmlDoc: any) => void; // 失败回调
};

3、封装功能

3.1 创建新类

创建一个新类,用以承接状态与功能。

// 通过依赖包,已挂载到 Window 下
const { $, WebVideoCtrl } = window as any;

export default class HikCamera {
    constructor() {
    }
    
    // 验证插件
    static VerifyPlugin(thing: any) {
        const type = Object.prototype.toString.call(thing);
        if (type !== '[object Function]') {
            return false;
        }
        return true;
    }

    // 抛出错误
    static ThrowError(message: string) {
        throw new Error(message);
    }
    
    // 销毁插件
    destroy = () => {
    };
}
3.2 插件初始化
// 通过依赖包,已挂载到 Window 下
const { $, WebVideoCtrl } = window as any;

export default class HikCamera {
    public iWndIndex: number; // 当前选中的窗口

    constructor() {
        this.iWndIndex = 0;
    }
    
    // 验证插件
    ...

    // 抛出错误
    ...
    
    // 销毁插件
    destroy = () => {
    };
    
    // 插件初始化
    initPlugin = ({ szWidth = '100%', szHight = '100%', ...options }: PluginOptions = {}) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_CheckPluginInstall)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        // 检查插件:是否已经安装
        const iRet = WebVideoCtrl.I_CheckPluginInstall();
        if (-1 == iRet) {
            HikCamera.ThrowError('插件未安装,双击 WebComponentsKit.exe 安装!');
            return;
        }

        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_InitPlugin)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!HikCamera.VerifyPlugin($)) {
            HikCamera.ThrowError('jquery 未加载成功!');
            return;
        }

        WebVideoCtrl.I_InitPlugin(szWidth, szHight, {
            bWndFull: true,
            iPackageType: 2,
            iWndowType: 1,
            bNoPlugin: true,
            cbSelWnd: (xmlDoc: any) => {
                const index = window.parseInt($(xmlDoc).find('SelectWnd').eq(0).text(), 10);
                this.iWndIndex = index;
            },
            cbDoubleClickWnd: (iWndIndex: number, bFullScreen: boolean) => {
                console.log('窗口双击回调事件函数 =>', { iWndIndex, bFullScreen });
            },
            cbEvent: (iEventType: number, iParam1: any, iParam2: any) => {
                if (iEventType == 2) {
                    console.log('回放结束 =>', { iParam1 });
                } else if (iEventType == -1) {
                    console.log('网络错误 =>', { iParam1 });
                } else if (iEventType == 3001) {
                    // clickStopRecord(g_szRecordType, iParam1);
                }
            },
            cbRemoteConfig: () => {
                console.log('远程配置库关闭回调!');
            },
            cbInitPluginComplete: () => {
                WebVideoCtrl.I_InsertOBJECTPlugin('videoPlugin');
                // 检查插件是否最新
                if (WebVideoCtrl.I_CheckPluginVersion() == -1) {
                    HikCamera.ThrowError('检测到新版插件,双击 WebComponentsKit.exe 安装!');
                    return;
                }
            },
            ...(options || {}),
        });
    };
    
    // 嵌入播放插件
    insertOBJECTPlugin = (elementId: string = 'videoWindow') => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_InsertOBJECTPlugin)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }
        WebVideoCtrl.I_InsertOBJECTPlugin(elementId); // 嵌入播放插件
    };

    // 更新插件窗口(监听窗口大小变化时调用)
    pluginResize = (width: number = 500, height: number = 300) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_Resize)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }
        WebVideoCtrl.I_Resize(width, height);
    };
}
3.3 设备登录
// 通过依赖包,已挂载到 Window 下
const { $, WebVideoCtrl } = window as any;

export default class HikCamera {
    public iWndIndex: number; // 当前选中的窗口
    public szDeviceIdentify: string; // 设备标识 => [IP]_[PORT]
    public analogChannels: { id: string; name: string }[]; // 模拟通道
    public digitalChannels: { id: string; name: string; online: boolean }[]; // 数字通道
    public zeroChannels: { id: string; name: string; online: boolean }[]; // 零通道
    public ports: { iDevicePort?: number; iHttpPort?: number; iRtspPort?: number }; // 端口

    constructor() {
        this.iWndIndex = 0;
        this.szDeviceIdentify = '';
        this.analogChannels = [];
        this.digitalChannels = [];
        this.zeroChannels = [];
        this.ports = { iHttpPort: 80, iRtspPort: 554 };
    }
    
    // 验证插件
    ...

    // 抛出错误
    ...
    
    // 销毁插件
    destroy = () => {
        const { szDeviceIdentify } = this;
        szDeviceIdentify && this.logoutDevice(szDeviceIdentify);
        this.iWndIndex = 0;
        this.szDeviceIdentify = '';
        this.analogChannels = [];
        this.digitalChannels = [];
        this.zeroChannels = [];
        this.ports = {};
    };
    
    // 插件初始化
    ...
    
    // 嵌入播放插件
    ...

    // 更新插件窗口(监听窗口大小变化时调用)
    ...

    // 设备登录
    loginDevice = ({
        szIP,
        szPrototocol,
        szPort,
        szUsername,
        szPassword,
        success,
        ...options
    }: LoginOptions) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_Login)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!szIP) {
            HikCamera.ThrowError('IP 不能为空!');
            return;
        }

        if (!szPort) {
            HikCamera.ThrowError('PORT 不能为空!');
            return;
        }

        if (!szUsername) {
            HikCamera.ThrowError('USERNAME 不能为空!');
            return;
        }

        if (!szPassword) {
            HikCamera.ThrowError('PASSWORD 不能为空!');
            return;
        }

        const szDeviceIdentify = szIP + '_' + szPort;
        this.szDeviceIdentify = szDeviceIdentify;
        const _this = this;

        const iRet = WebVideoCtrl.I_Login(
            szIP,
            szPrototocol || 1,
            szPort || 80,
            szUsername,
            szPassword,
            {
                success: (xmlDoc: any) => {
                    setTimeout(() => {
                          _this.getAnalogChannelInfo(szDeviceIdentify);
                          _this.getDigitalChannelInfo(szDeviceIdentify);
                          _this.getZeroChannelInfo(szDeviceIdentify);
                          _this.getDevicePort(szDeviceIdentify);
                    }, 10);
                    success?.(xmlDoc);
                },
                error: () => {
                    HikCamera.ThrowError('设备登录失败!');
                    this.szDeviceIdentify = '';
                    this.analogChannels = [];
                    this.digitalChannels = [];
                    this.zeroChannels = [];
                    this.ports = {};
                },
                ...(options || {}),
            }
        );

        if (iRet == -1) {
            HikCamera.ThrowError('已登录过!');
        }
    };
    
    // 模拟通道
    getAnalogChannelInfo = (szDeviceIdentify: string, options?: {}) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetAnalogChannelInfo)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!szDeviceIdentify) {
            HikCamera.ThrowError('设备标识不能为空!');
            return;
        }

        const _this = this;

        WebVideoCtrl.I_GetAnalogChannelInfo(szDeviceIdentify, {
            async: false,
            success: (xmlDoc: any) => {
                const OPTIONS: { id: string; name: string }[] = []; // 通道列表
                const channelElements = $(xmlDoc).find('VideoInputChannel');
                $.each(channelElements, () => {
                    const id = $(this).find('id').eq(0).text();
                    const name = $(this).find('name').eq(0).text();
                    OPTIONS.push({ id, name });
                });
                console.log('获取模拟通道 =>', { OPTIONS });
                _this.analogChannels = OPTIONS;
            },
            error: (status: any, xmlDoc: any) => {
                console.log('模拟通道取失败 =>', { status, xmlDoc });
            },
            ...(options || {}),
        });
    };

    // 数字通道
    getDigitalChannelInfo = (szDeviceIdentify: string, options?: {}) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetDigitalChannelInfo)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!szDeviceIdentify) {
            HikCamera.ThrowError('设备标识不能为空!');
            return;
        }

        const _this = this;

        WebVideoCtrl.I_GetDigitalChannelInfo(szDeviceIdentify, {
            async: false,
            success: (xmlDoc: any) => {
                const OPTIONS: { id: string; name: string; online: boolean }[] = []; // 通道列表
                const channelElements = $(xmlDoc).find('InputProxyChannelStatus');
                $.each(channelElements, () => {
                    const id = $(this).find('id').eq(0).text();
                    const name = $(this).find('name').eq(0).text();
                    const online = $(this).find('online').eq(0).text(); // 是否在线
                    OPTIONS.push({ id, name, online: online === 'false' ? false : true });
                });
                console.log('获取数字通道 =>', { OPTIONS });
                _this.digitalChannels = OPTIONS;
            },
            error: (status: any, xmlDoc: any) => {
                console.log('数字通道取失败 =>', { status, xmlDoc });
            },
            ...(options || {}),
        });
    };

    // 零通道
    getZeroChannelInfo = (szDeviceIdentify: string, options?: {}) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetZeroChannelInfo)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!szDeviceIdentify) {
            HikCamera.ThrowError('设备标识不能为空!');
            return;
        }

        const _this = this;

        WebVideoCtrl.I_GetZeroChannelInfo(szDeviceIdentify, {
            async: false,
            success: (xmlDoc: any) => {
                const OPTIONS: { id: string; name: string; online: boolean }[] = []; // 通道列表
                const channelElements = $(xmlDoc).find('ZeroVideoChannel');
                $.each(channelElements, () => {
                    const id = $(this).find('id').eq(0).text();
                    const name = $(this).find('name').eq(0).text();
                    const online = $(this).find('enabled').eq(0).text(); // 是否在线
                    OPTIONS.push({ id, name, online: online === 'false' ? false : true });
                });
                console.log('获取零通道 =>', { OPTIONS });
                _this.zeroChannels = OPTIONS;
            },
            error: (status: any, xmlDoc: any) => {
                console.log('零通道获取失败 =>', { status, xmlDoc });
            },
            ...(options || {}),
        });
    };
    
    // 获取设备端口
    getDevicePort = (szDeviceIdentify: string) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetDevicePort)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!szDeviceIdentify) {
            HikCamera.ThrowError('设备标识不能为空!');
            return;
        }

        const oPort = WebVideoCtrl.I_GetDevicePort(szDeviceIdentify);

        if (oPort?.iDevicePort && oPort?.iRtspPort) {
            this.ports = oPort;
            console.log('端口获取成功 =>', { oPort });
        } else {
            HikCamera.ThrowError('端口获取失败!');
        }

        return oPort;
    };

    // 设备登出
    logoutDevice = (szDeviceIdentify: string) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_Logout)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!szDeviceIdentify) {
            HikCamera.ThrowError('设备标识不能为空!');
            return;
        }

        const iRet = WebVideoCtrl.I_Logout(szDeviceIdentify);
        if (iRet == 0) {
            console.log('退出成功!');
            this.szDeviceIdentify = '';
            this.analogChannels = [];
            this.digitalChannels = [];
            this.zeroChannels = [];
            this.ports = {};
        } else {
            console.log('退出失败!');
        }
    };
}
3.4 直播预览
// 通过依赖包,已挂载到 Window 下
const { $, WebVideoCtrl } = window as any;

export default class HikCamera {
    public iWndIndex: number; // 当前选中的窗口
    ...

    constructor() {
        ...
    }
    
    // 验证插件
    ...

    // 抛出错误
    ...
    
    // 销毁插件
    destroy = () => {
        const { szDeviceIdentify } = this;
        this.stopPlay();
        ...
    };
    
    // 插件初始化
    ...
    
    // 嵌入播放插件
    ...

    // 更新插件窗口(监听窗口大小变化时调用)
    ...

    // 设备登录
    ...
    
    // 模拟通道
    ...

    // 数字通道
    ...

    // 零通道
    ...

    // 获取设备端口
    ...

    // 设备登出
    ...
    
    // 直播:开始预览
    startRealPlay = (
        szDeviceIdentify: string,
        { bZeroChannel, iChannelID, iPort, iStreamType, ...options }: StartRealPlayOptions
    ) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetWindowStatus)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_Stop)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_StartRealPlay)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!szDeviceIdentify) {
            HikCamera.ThrowError('设备标识不能为空!');
            return;
        }

        const { iRtspPort } = this.ports;

        // ### 播放 [Begin]
        const play = () => {
            WebVideoCtrl.I_StartRealPlay(szDeviceIdentify, {
                ...(iPort ? { iRtspPort: iPort || iRtspPort } : {}),
                ...(iPort ? { iPort: iPort || iRtspPort } : {}),
                iStreamType: iStreamType || 1,
                iChannelID: iChannelID || 1,
                bZeroChannel: bZeroChannel || false,
                success: () => {
                    console.log('播放成功!');
                },
                error: (status: any, xmlDoc: any) => {
                    if (status === 403) {
                        HikCamera.ThrowError('设备不支持 Websocket 取流服务!');
                    } else {
                        HikCamera.ThrowError('播放失败!');
                    }
                },
                ...(options || {}),
            });
        };

        const { iWndIndex } = this;
        const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);

        if (oWndInfo) {
            // 已经在播放,先停止
            WebVideoCtrl.I_Stop({ success: play });
        } else {
            play();
        }
    };
    
    // 停止播放
    stopPlay = (options?: StopRealPlayOptions) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_Stop)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetWindowStatus)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        const { iWndIndex } = this;
        const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);

        if (oWndInfo) {
            WebVideoCtrl.I_Stop(options || {});
        }
    };
}
3.5 录像回放
// 通过依赖包,已挂载到 Window 下
const { $, WebVideoCtrl } = window as any;

// 回放:转码流配置
export const TRANS_CODE_PARAM = {
      TransFrameRate: '14', // 0:全帧率,5:1,6:2,7:4,8:6,9:8,10:10,11:12,12:16,14:15,15:18,13:20,16:22
      TransResolution: '1', // 255:Auto,3:4CIF,2:QCIF,1:CIF
      TransBitrate: '19', // 2:32K,3:48K,4:64K,5:80K,6:96K,7:128K,8:160K,9:192K,10:224K,11:256K,12:320K,13:384K,14:448K,15:512K,16:640K,17:768K,18:896K,19:1024K,20:1280K,21:1536K,22:1792K,23:2048K,24:3072K,25:4096K,26:8192K
};

export default class HikCamera {
    ...

    // 停止播放
    ...
    
    
    // 开始回放
    startPlayback = (
        szDeviceIdentify: string,
        {
        iWndIndex,
        iStreamType,
        iPort,
        szStartTime,
        szEndTime,
        iChannelID,
        ...options
        }: StartPlayBackOptions
    ) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetWindowStatus)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!szDeviceIdentify) {
            HikCamera.ThrowError('设备标识不能为空!');
            return;
        }

        const bZeroChannel = false;
        if (bZeroChannel) {
            HikCamera.ThrowError('零通道不支持回放!');
            return;
        }

        const { iWndIndex: _iWndIndex } = this;
        const { iRtspPort } = this.ports;

        const play = () => {
            WebVideoCtrl.I_StartPlayback(szDeviceIdentify, {
                iWndIndex: iWndIndex || _iWndIndex || 0,
                iStreamType: iStreamType || 1,
                iPort: iPort || iRtspPort || 554,
                iRtspPort: iPort || iRtspPort || 554,
                szStartTime: szStartTime || '2013-12-23 00:00:00',
                szEndTime: szEndTime || '2013-12-23 23:59:59',
                iChannelID: iChannelID || 1,
                // oTransCodeParam: TRANS_CODE_PARAM,
                success: () => {
                    console.log('回放成功!');
                },
                error: (status: any, xmlDoc: any) => {
                    if (status === 403) {
                        HikCamera.ThrowError('设备不支持 Websocket 取流服务!');
                    } else {
                        HikCamera.ThrowError('回放失败!');
                    }
                },
                ...(options || {}),
            });
        };

        const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex || _iWndIndex || 0);

        if (oWndInfo) {
            // 已经在播放,先停止
            WebVideoCtrl.I_Stop({ success: play });
        } else {
            play();
        }
    };

    // 减速播放
    showPlay = (options?: StopRealPlayOptions) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_PlaySlow)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetWindowStatus)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        const { iWndIndex } = this;
        const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);

        if (oWndInfo) {
            WebVideoCtrl.I_PlaySlow(options || {});
        }
    };

    // 加速播放
    falsePlay = (options?: StopRealPlayOptions) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_PlayFast)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetWindowStatus)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        const { iWndIndex } = this;
        const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);

        if (oWndInfo) {
            WebVideoCtrl.I_PlayFast(options || {});
        }
    };

    // 恢复播放
    resumePlay = (options?: StopRealPlayOptions) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_Resume)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetWindowStatus)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        const { iWndIndex } = this;
        const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);

        if (oWndInfo) {
            WebVideoCtrl.I_Resume(options || {});
        }
    };

    // 暂停播放
    pausePlay = (options?: StopRealPlayOptions) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_Pause)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetWindowStatus)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        const { iWndIndex } = this;
        const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);

        if (oWndInfo) {
            WebVideoCtrl.I_Pause(options || {});
        }
    };
}
3.6 录像搜索
// 通过依赖包,已挂载到 Window 下
const { $, WebVideoCtrl } = window as any;

...

export default class HikCamera {
    ...

    // 暂停播放
    ...
    
    
    // 搜索录像
    searchRecord = (
        szDeviceIdentify: string,
        {
            iChannelID = 1,
            szStartTime,
            szEndTime,
            iStreamType = 1,
            success,
            error,
            pageIndex,
            pageSize,
            ...options
        }: SearchRecordOptions
    ) => {
        const { szDeviceIdentify: _szDeviceIdentify } = this;

        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_RecordSearch)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!szDeviceIdentify && !_szDeviceIdentify) {
            HikCamera.ThrowError('设备标识不能为空!');
            return;
        }

        if (!iChannelID || !szStartTime || !szEndTime) {
            HikCamera.ThrowError('参数不能为空!');
            return;
        }

        const startTime = Date.parse(szStartTime.replace(/-/g, '/'));
        const endTime = Date.parse(szEndTime.replace(/-/g, '/'));
        if (endTime - startTime < 0) {
            HikCamera.ThrowError('开始时间大于结束时间!');
            return;
        }

        const bZeroChannel = false;
        if (bZeroChannel) {
            HikCamera.ThrowError('零通道不支持搜索!');
            return;
        }

        const RecordList: {
            total: string; // 总数
            szStartTime: string; // 开始时间
            szEndTime: string; // 结束时间
            szFileName: string; // 文件名
            szPlaybackURI: string; // 下载流
        }[] = [];

        // 搜索
        const search = () => {
            WebVideoCtrl.I_RecordSearch(
                szDeviceIdentify || _szDeviceIdentify,
                iChannelID,
                szStartTime,
                szEndTime,
                {
                    iStreamType: iStreamType || 1,
                    iSearchPos: (pageIndex || 0) * (pageSize || 40),
                    success: (xmlDoc: any) => {
                        const IsLastPage = $(xmlDoc).find('responseStatusStrg').eq(0).text();
                        if (IsLastPage === 'MORE') {
                            const iLength = $(xmlDoc).find('searchMatchItem').length;
                            for (let i = 0, nLen = iLength; i < nLen; i++) {
                                const szPlaybackURI = $(xmlDoc).find('playbackURI').eq(i).text();
                                if (szPlaybackURI.indexOf('name=') < 0) break;
                                const szStartTime = $(xmlDoc).find('startTime').eq(i).text();
                                const szEndTime = $(xmlDoc).find('endTime').eq(i).text();
                                const szFileName = szPlaybackURI.substring(
                                    szPlaybackURI.indexOf('name=') + 5,
                                    szPlaybackURI.indexOf('&size=')
                                );
                                RecordList.push({ szStartTime, szEndTime, szFileName, szPlaybackURI });
                            }
                            success?.(RecordList);
                        } else if (IsLastPage === 'OK') {
                            const iLength = $(xmlDoc).find('searchMatchItem').length;
                            for (let i = 0; i < iLength; i++) {
                                const szPlaybackURI = $(xmlDoc).find('playbackURI').eq(i).text();
                                if (szPlaybackURI.indexOf('name=') < 0) break;
                                const szStartTime = $(xmlDoc).find('startTime').eq(i).text();
                                const szEndTime = $(xmlDoc).find('endTime').eq(i).text();
                                const szFileName = szPlaybackURI.substring(
                                    szPlaybackURI.indexOf('name=') + 5,
                                    szPlaybackURI.indexOf('&size=')
                                );
                                RecordList.push({ szStartTime, szEndTime, szFileName, szPlaybackURI }); 
                            }
                            success?.(RecordList);
                            console.log('搜索完成!');
                        } else if (IsLastPage === 'NO MATCHES') {
                            setTimeout(() => {
                                HikCamera.ThrowError('未检索到相关内容!');
                            }, 50);
                        }
                    },
                    error: (status: any, xmlDoc: any) => {
                        console.error('检索出错!');
                        error?.(status, xmlDoc);
                    },
                }
            );
        };

        search();
    };
}
3.7 录像下载
// 通过依赖包,已挂载到 Window 下
const { $, WebVideoCtrl } = window as any;

...

export default class HikCamera {
    ...

    // 搜索录像
    ...

    // 下载录像(按时间)
    downloadRecordByTime = (
        szDeviceIdentify: string,
        { szPlaybackURI, szFileName, szStartTime, szEndTime, ...options }: DownloadRecordOptions
    ) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_StartDownloadRecordByTime)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetLastError)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }

        if (!szDeviceIdentify) {
            HikCamera.ThrowError('设备标识不能为空!');
            return;
        }

        if (!szPlaybackURI || !szFileName || !szStartTime || !szEndTime) {
            HikCamera.ThrowError('参数不能为空!');
            return;
        }

        const startTime = Date.parse(szStartTime.replace(/-/g, '/'));
        const endTime = Date.parse(szEndTime.replace(/-/g, '/'));
        if (endTime - startTime < 0) {
            HikCamera.ThrowError('开始时间大于结束时间!');
            return;
        }

        let downloadId = -1; // 下载 ID
        downloadId = WebVideoCtrl.I_StartDownloadRecordByTime(
            szDeviceIdentify,
            szPlaybackURI,
            szFileName,
            szStartTime,
            szEndTime,
            {
                bDateDir: true,
                ...(options || {}),
            }
        );

        if (downloadId < 0) {
            const errorStatus = WebVideoCtrl.I_GetLastError();
            if (errorStatus == 34) {
                console.log('下载成功!');
            } else if (errorStatus == 33) {
                HikCamera.ThrowError('空间不足!');
            } else {
                HikCamera.ThrowError('下载失败!');
            }
            return { downloadId, errorStatus };
        } else {
            console.log('下载中...');
            return { downloadId };
        }
    };

    // 下载状态
    downloadStatus = (downloadId: number) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetDownloadStatus)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }
        const result = WebVideoCtrl.I_GetDownloadStatus(downloadId);
        return result;
    };

    // 下载进度
    downloadProgress = (downloadId: number) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetDownloadProgress)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }
        const result = WebVideoCtrl.I_GetDownloadProgress(downloadId);
        return result;
    };
}
2.5 其它补充
// 通过依赖包,已挂载到 Window 下
const { $, WebVideoCtrl } = window as any;

...

export default class HikCamera {
    ...

    // 下载进度
    ...

    // 检查浏览器是否支持无插件
    supportNoPlugin = () => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_SupportNoPlugin)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }
        const result = WebVideoCtrl.I_SupportNoPlugin();
        return result;
    };

    //  检查插件是否已安装
    checkPluginInstall = () => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_CheckPluginInstall)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }
        const result = WebVideoCtrl.I_CheckPluginInstall();
        if (result === 0) {
            return true;
        } else if (result === -1) {
            return false;
        } else {
            return false;
        }
    };

    // 获取设备基本信息
    getDeviceInfo = (szDeviceIdentify: string, options = {}) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetDeviceInfo)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }
        if (!szDeviceIdentify) {
            HikCamera.ThrowError('设备标识不能为空!');
            return;
        }
        WebVideoCtrl.I_GetDeviceInfo(szDeviceIdentify, options);
    };

    // 获取播放窗口信息
    getWindowStatus = (iWndIndex: number = this?.iWndIndex || 0) => {
        if (!HikCamera.VerifyPlugin(WebVideoCtrl?.I_GetWindowStatus)) {
            HikCamera.ThrowError('webVideoCtrl 未加载成功!');
            return;
        }
        const oWndInfo = WebVideoCtrl.I_GetWindowStatus(iWndIndex);
        return oWndInfo;
    };
}

4、使用示例(React)

4.1 引入依赖
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="theme-color" content="#000000" />
        <meta name="description" content="Web site created using create-react-app" />
        <title>海康设备视频服务</title>
    </head>

    <body>
        <div id="root"></div>
        <!-- 必要的依赖包 -->
        <script src="/jquery-1.7.1.min.js"></script>
        <script src="/encryption/AES.js"></script>
        <script src="/encryption/cryptico.min.js"></script>
        <script src="/encryption/crypto-3.1.2.min.js"></script>
        <script src="/webVideoCtrl.js"></script>
    </body>
</html>

4.2 视频示例
import { useReducer, useRef, useEffect } from 'react';
import moment from 'moment';
import HikCamera from './HikCamera';

const hikCamera = new HikCamera();

const Fragment = ({}) => {
    const videoRef = useRef(null);
    const szStartTime = moment().subtract(30, 'minutes').format('YYYY-MM-DD HH:mm:ss');
    const szEndTime = moment().format('YYYY-MM-DD HH:mm:ss');

    // 窗口大小改变
    const handleWindowResize = () => {
        if (!videoRef?.current) return;
        setTimeout(() => {
            const width = (videoRef.current as any)?.offsetWidth || 500;
            const height = (videoRef.current as any)?.offsetHeight || 300;
            hikCamera.pluginResize(width, height);
        }, 10);
    };
    
    useEffect(() => {
        // ### 载入插件
        setTimeout(() => {
            const szWidth = (videoRef.current as any)?.offsetWidth || 500;
            const szHight = (videoRef.current as any)?.offsetHeight || 300;
            const playbackVideoDiv: any = document.getElementById('playbackVideo');
            playbackVideoDiv.style.height = szHight + 'px';
            playbackVideoDiv.style.width = szWidth + 'px';
            hikCamera.initPlugin({
                szWidth,
                szHight,
                cbInitPluginComplete: () => {
                    hikCamera.insertOBJECTPlugin('playbackVideo');
                },
            });
            window.addEventListener('resize', handleWindowResize);
        }, 10);

        // ### 销毁插件
        return () => {
            hikCamera.destroy();
            window.removeEventListener('resize', handleWindowResize);
        };
    }, []);
    
    // 登录设备
    const login = (success = () => {}) => {
        hikCamera.resumePlay();
        hikCamera.stopPlay();
        hikCamera.logoutDevice(hikCamera.szDeviceIdentify); // 退出正在播放的设备

        hikCamera.loginDevice({
            szIP: '192.168.1.10',
            szPort: 80,
            szUsername: 'username',
            szPassword: 'password',
            success: () => {
                console.log('登录成功!');
                success();
            },
            error: () => {
                console.log('登录失败!');
                hikCamera.szDeviceIdentify = '';
                hikCamera.analogChannels = [];
                hikCamera.digitalChannels = [];
                hikCamera.zeroChannels = [];
                hikCamera.ports = {};
            },
        });
    };
    
    // 下载视频
        const download = (
            szDeviceIdentify,
            {
                szStartTime,
                szEndTime,
                szFileName,
                szPlaybackURI,
            }
        ) => {
        const { downloadId, errorStatus } = hikCamera.downloadRecordByTime(szDeviceIdentify, {
            szPlaybackURI,
            szStartTime,
            szEndTime,
            szFileName,
        });
        if (downloadId < 0) {
            if (errorStatus == 34) {
                console.log('下载完成!');
            } else if (errorStatus == 33) {
                console.log('空间不足!');
            } else {
                message.error('下载失败!');
            }
            console.log('下载结果 =>', { downloadId, errorStatus });
        } else {
            message.success('开始下载...');
            getDownloadProgress(downloadId);
        }
    };

    return (
        <div style={{ height: '100vh', width: '100vh' }}>
            <div style={{ height: '80%', width: '100%' }} ref={videoRef}>
                <div id="playbackVideo"></div>
            </div>
            <button onClick={() => {
                login(() => {
                    const { szDeviceIdentify } = hikCamera;
                    if (szDeviceIdentify) {
                        hikCamera.startRealPlay(szDeviceIdentify, {
                            iPort: 'iPort',
                            iChannelID: 1,
                            iStreamType: 1,
                            success: () => {
                                console.log('播放成功!');
                            },
                            error: (status: any) => {
                                if (status === 403) {
                                    console.log('设备不支持 Websocket 取流服务!');
                                } else {
                                    console.log('播放失败!');
                                }
                            },
                        });
                    } else {
                        console.log('无法连接设备!');
                    }
                });
            }}>直播预览</button>
            <button onClick={() => {
                login(() => {
                    const { szDeviceIdentify } = hikCamera;
                    if (szDeviceIdentify) {
                        hikCamera.startPlayback(szDeviceIdentify, {
                            iPort: 'iPort',
                            iStreamType: 1,
                            iChannelID: 1,
                            szStartTime,
                            szEndTime,
                            success: () => {
                                console.log('回放成功!');
                            },
                            error: (status: any) => {
                                if (status === 403) {
                                    console.log('设备不支持 Websocket 取流服务!');
                                } else {
                                    console.log('播放失败!');
                                }
                            },
                        });
                    } else {
                        console.log('无法连接设备!');
                    }
                });
            }}>录像回放</button>
            <button onClick={() => {
                login(() => {
                    const { szDeviceIdentify } = hikCamera;
                    if (szDeviceIdentify) {
                        hikCamera.searchRecord(szDeviceIdentify, {
                            szStartTime,
                            szEndTime,
                            success: (RecordList) => {
                                const { szStartTime, szEndTime, szFileName, szPlaybackURI } = RecordList?.[0] || {};
                                download(szDeviceIdentify, {
                                    szStartTime,
                                    szEndTime,
                                    szFileName,
                                    szPlaybackURI,
                                });
                            },
                            error: () => {
                                console.log('下载内容获取失败!');
                            },
                        });
                    } else {
                        console.log('无法连接设备!');
                    }
                });
            }}>录像下载</button>
            <button onClick={() => {
                hikCamera.resumePlay();
                hikCamera.stopPlay();
            }}>停止播放</button>
            <button onClick={() => hikCamera.pausePlay()}>暂停播放</button>
            <button onClick={() => hikCamera.falsePlay()}>快进</button>
            <button onClick={() => hikCamera.showPlay()}>慢放</button>
        </div>
    );
}

export default Fragment;