ZLMediaKit+SpringBoot+Vue+Geoserver实现拉取摄像头rtsp流并在web端播放

场景

SpringBoot+Vue+Openlayers实现地图上新增和编辑坐标并保存提交:

SpringBoot+Vue+Openlayers实现地图上新增和编辑坐标并保存提交_霸道流氓气质的博客-CSDN博客

开源流媒体服务器ZLMediaKit在Windows上运行、配置、按需拉流拉取摄像头rtsp视频流)并使用http-flv网页播放:

开源流媒体服务器ZLMediaKit在Windows上运行、配置、按需拉流拉取摄像头rtsp视频流)并使用http-flv网页播放_srs按需拉流_霸道流氓气质的博客-CSDN博客

GeoServer简介、下载、配置启动、发布shapefile全流程(图文实践):

GeoServer简介、下载、配置启动、发布shapefile全流程(图文实践)_霸道流氓气质的博客-CSDN博客

若依前后端分离版手把手教你本地搭建环境并运行项目:

若依前后端分离版手把手教你本地搭建环境并运行项目_ruoyi本地调式_霸道流氓气质的博客-CSDN博客

结合以上流程,需要实现对摄像头名称、在地图上位置的增删改查以及摄像头的预览功能。

注意这里的摄像头只支持H264编码格式的拉流和播放。

实现摄像头预览效果

地图上新增和编辑坐标效果

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi 

实现

1、参考以上搭建前后端分离框架,建表如下

 

建表语句:

DROP TABLE IF EXISTS `bus_stream_media_video`;
CREATE TABLE `bus_stream_media_video`  (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '序号',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称',
  `rtsp_stream_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'rtsp流地址',
  `area_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '位置',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 26 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '流媒体摄像头' ROW_FORMAT = DYNAMIC;

2、摄像头拉流预览流程

选择某个摄像头进行预览时,先校验流媒体相关信息是否设置,是才能预览

拿着该条数据的rtsp流地址调用ZLMediaKit的拉流的api进行拉流。

api拼接规则:

http://流媒体服务ip:流媒体服务端口/index/api/addStreamProxy?vhost=流媒体服务ip&app=live&stream=拼接时间戳&url=摄像头rtsp地址&secret= 流媒体服务秘钥

调用拉流api时需要流媒体服务器的ip和端口以及秘钥信息,通过配置页面进行配置并进行修改时的二次密码校验

然后解析拉流接口返回的json数据

状态码不为0或接口不通无响应等则提示不可预览,状态码为0则可预览。

解析data下的key,按照/解析,比如上面解析出ip:127.0.0.1,流应用live,流id为test。

进行http-flv视频预览, 预览url拼接规则:

http://key解析的ip:流媒体服务端口/key解析的live/key解析的test.flv

这里的流程可参考上面博客。

2、使用框架自带的代码生成工具生成代码并进行实体类部分修改

摄像头实体类:

import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class BusStreamMediaVideo extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** 序号 */
    private Long id;

    /** 名称 */
    @Excel(name = "名称")
    private String name;

    /** 位置x坐标 */
    private BigDecimal siteX;

    /** 位置y坐标 */
    private BigDecimal siteY;

    /** 区域位置 */
    @Excel(name = "区域位置")
    private String areaName;

    /** RTSP addresss */
    @Excel(name = "rtspAddress")
    private String rtspStreamAddress;

    private BigDecimal[] videoAdd;

}

流媒体设置DTO类:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class BusStreamMediaVideoParamDto
{

    private String ip;

    private String port;

    private String secret;

}

Mapper接口层:

import com.ruoyi.system.domain.BusStreamMediaVideo;
import com.ruoyi.system.redisAop.AopCacheEnable;
import com.ruoyi.system.redisAop.AopCacheEvict;
import java.util.List;

public interface BusStreamMediaVideoMapper
{
    /**
     * 查询摄像头参数
     *
     * @param id 摄像头参数ID
     * @return 摄像头参数
     */
        public BusStreamMediaVideo selectBusStreamMediaVideoById(Long id);

    /**
     * 查询摄像头参数列表
     *
     * @param
     * @return 摄像头参数集合
     */
    @AopCacheEnable(key = "busStreamMediaVideo",expireTime = 86400)
    public List<BusStreamMediaVideo> selectBusStreamMediaVideoList(BusStreamMediaVideo busStreamMediaVideo);

    /**
     * 新增摄像头参数
     *
     * @param
     * @return 结果
     */
    @AopCacheEvict(key = "busStreamMediaVideo")
    public int insertBusStreamMediaVideo(BusStreamMediaVideo busStreamMediaVideo);

    /**
     * 修改摄像头参数
     *
     * @param
     * @return 结果
     */
    @AopCacheEvict(key = "busStreamMediaVideo")
    public int updateBusStreamMediaVideo(BusStreamMediaVideo busStreamMediaVideo);

    /**
     * 删除摄像头参数
     *
     * @param id 摄像头参数ID
     * @return 结果
     */
    @AopCacheEvict(key = "busStreamMediaVideo")
    public int deleteBusStreamMediaVideoById(Long id);

    /**
     * 批量删除摄像头参数
     *
     * @param ids 需要删除的数据ID
     * @return 结果
     */
    @AopCacheEvict(key = "busStreamMediaVideo")
    public int deleteBusStreamMediaVideoByIds(Long[] ids);
}

注意这里使用了自定义缓存注解。

SpringBoot中通过自定义缓存注解(AOP切面拦截)实现数据库数据缓存到Redis:

SpringBoot中通过自定义缓存注解(AOP切面拦截)实现数据库数据缓存到Redis_redis切面_霸道流氓气质的博客-CSDN博客

Mapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.BusStreamMediaVideoMapper">

    <resultMap type="BusStreamMediaVideo" id="BusStreamMediaVideoResult">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="areaName" column="area_name"/>
        <result property="rtspStreamAddress" column="rtsp_stream_address"/>
    </resultMap>

    <sql id="selectBusStreamMediaVideoVo">
        select id, name, area_name, rtsp_stream_address
        from bus_stream_media_video
    </sql>

    <select id="selectBusStreamMediaVideoList" parameterType="BusStreamMediaVideo"
            resultMap="BusStreamMediaVideoResult">
        <include refid="selectBusStreamMediaVideoVo"/>
        <where>
            <if test="name != null  and name != ''">and name = #{name}</if>
            <if test="areaName!=null and areaName != ''">and area_name = #{areaName}</if>
            <if test="rtspStreamAddress!=null and rtspStreamAddress != ''">and rtsp_stream_address = #{rtspStreamAddress}</if>
        </where>
    </select>

    <select id="selectBusStreamMediaVideoById" parameterType="Long" resultMap="BusStreamMediaVideoResult">
        <include refid="selectBusStreamMediaVideoVo"/>
        where id = #{id}
    </select>

    <insert id="insertBusStreamMediaVideo" parameterType="BusStreamMediaVideo">
        insert into bus_stream_media_video
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">id,</if>
            <if test="name != null">name,</if>
            <if test="areaName != null">area_name,</if>
            <if test="rtspStreamAddress != null">rtsp_stream_address,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="id != null">#{id},</if>
            <if test="name != null">#{name},</if>
            <if test="areaName != null">#{areaName},</if>
            <if test="rtspStreamAddress != null">#{rtspStreamAddress},</if>
        </trim>
    </insert>

    <update id="updateBusStreamMediaVideo" parameterType="BusStreamMediaVideo">
        update bus_stream_media_video
        <trim prefix="SET" suffixOverrides=",">
            <if test="name != null">name = #{name},</if>
            <if test="areaName!=null">area_name =#{areaName},</if>
            <if test="rtspStreamAddress!=null">rtsp_stream_address =#{rtspStreamAddress},</if>
        </trim>
        where id = #{id}
    </update>

    <delete id="deleteBusStreamMediaVideoById" parameterType="Long">
        delete
        from bus_stream_media_video
        where id = #{id}
    </delete>

    <delete id="deleteBusStreamMediaVideoByIds" parameterType="String">
        delete from bus_stream_media_video where id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

</mapper>

Service接口:

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.domain.BusStreamMediaVideo;
import com.ruoyi.system.domain.BusStreamMediaVideoParamDto;
import java.util.List;

public interface IBusStreamMediaVideoService
{

    /**
     * 查询流媒体服务器参数
     * @return
     */
    public BusStreamMediaVideoParamDto getStreamMediaParam();
    /**
     * 设置流媒体参数
     * @param busStreamMediaVideoParamDto
     * @return
     */
    public AjaxResult setStreamMediaParam(BusStreamMediaVideoParamDto busStreamMediaVideoParamDto);

    /**
     * 查询摄像头参数
     *
     * @param id 摄像头参数ID
     * @return 摄像头参数
     */
    public BusStreamMediaVideo selectBusStreamMediaVideoById(Long id);

    /**
     * 查询摄像头参数列表
     *
     * @param BusStreamMediaVideo 摄像头参数
     * @return 摄像头参数集合
     */
    public List<BusStreamMediaVideo> selectBusStreamMediaVideoList(BusStreamMediaVideo BusStreamMediaVideo);

    /**
     * 新增摄像头参数
     *
     * @param BusStreamMediaVideo 摄像头参数
     * @return 结果
     */
    public int insertBusStreamMediaVideo(BusStreamMediaVideo BusStreamMediaVideo);

    /**
     * 修改摄像头参数
     *
     * @param BusStreamMediaVideo 摄像头参数
     * @return 结果
     */
    public int updateBusStreamMediaVideo(BusStreamMediaVideo BusStreamMediaVideo);

    /**
     * 批量删除摄像头参数
     *
     * @param ids 需要删除的摄像头参数ID
     * @return 结果
     */
    public int deleteBusStreamMediaVideoByIds(Long[] ids);

    /**
     * 删除摄像头参数信息
     *
     * @param id 摄像头参数ID
     * @return 结果
     */
    public int deleteBusStreamMediaVideoById(Long id);
}

ServiceImpl:

import com.ruoyi.common.constant.RedisPTConstants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.system.domain.BusStreamMediaVideo;
import com.ruoyi.system.domain.BusStreamMediaVideoParamDto;
import com.ruoyi.system.mapper.BusStreamMediaVideoMapper;
import com.ruoyi.system.service.IBusStreamMediaVideoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;

@Service
public class BusStreamMediaVideoServiceImpl implements IBusStreamMediaVideoService {

    @Autowired
    private BusStreamMediaVideoMapper busStreamMediaVideoMapper;

    @Autowired
    private RedisCache redisService;

    @Override
    public BusStreamMediaVideoParamDto getStreamMediaParam() {
        if(redisService.hasKey(RedisPTConstants.STREAMMEDIAPARAM)){
            BusStreamMediaVideoParamDto busStreamMediaVideoParamDto = redisService.getCacheObject(RedisPTConstants.STREAMMEDIAPARAM);
            return busStreamMediaVideoParamDto;
        }else {
            return new BusStreamMediaVideoParamDto();
        }
    }

    @Override
    public AjaxResult setStreamMediaParam(BusStreamMediaVideoParamDto busStreamMediaVideoDto) {
        try {
            redisService.setCacheObject(RedisPTConstants.STREAMMEDIAPARAM,busStreamMediaVideoDto);
            return AjaxResult.success();
        }catch (Exception exception){
            return AjaxResult.error();
        }
    }

    /**
     * 查询摄像头参数
     *
     * @param id 摄像头参数ID
     * @return 摄像头参数
     */
    @Override
    public BusStreamMediaVideo selectBusStreamMediaVideoById(Long id) {
        return busStreamMediaVideoMapper.selectBusStreamMediaVideoById(id);
    }

    /**
     * 查询摄像头参数列表
     *
     * @param BusStreamMediaVideo 摄像头参数
     * @return 摄像头参数
     */
    @Override
    public List<BusStreamMediaVideo> selectBusStreamMediaVideoList(BusStreamMediaVideo BusStreamMediaVideo) {
        List<BusStreamMediaVideo> BusStreamMediaVideos = busStreamMediaVideoMapper.selectBusStreamMediaVideoList(BusStreamMediaVideo);
        BusStreamMediaVideos.forEach(videos -> {
            if (videos.getAreaName() != null) {
                String[] point = videos.getAreaName().substring(1, videos.getAreaName().length() - 1).split(",");
                if (!point[0].equals("null") && !point[1].equals("null")) {
                    videos.setSiteX(new BigDecimal(point[0]));
                    videos.setSiteY(new BigDecimal(point[1]));
                }
            }
        });
        return BusStreamMediaVideos;
    }

    /**
     * 新增摄像头参数
     *
     * @param BusStreamMediaVideo 摄像头参数
     * @return 结果
     */
    @Override
    public int insertBusStreamMediaVideo(BusStreamMediaVideo BusStreamMediaVideo) {

        String area = "[" + BusStreamMediaVideo.getSiteX() + "," + BusStreamMediaVideo.getSiteY() + "]";
        BusStreamMediaVideo.setAreaName(area);
        return busStreamMediaVideoMapper.insertBusStreamMediaVideo(BusStreamMediaVideo);
    }

    /**
     * 修改摄像头参数
     *
     * @param BusStreamMediaVideo 摄像头参数
     * @return 结果
     */
    @Override
    public int updateBusStreamMediaVideo(BusStreamMediaVideo BusStreamMediaVideo) {
        String area = "[" + BusStreamMediaVideo.getSiteX() + "," + BusStreamMediaVideo.getSiteY() + "]";
        BusStreamMediaVideo.setAreaName(area);
        return busStreamMediaVideoMapper.updateBusStreamMediaVideo(BusStreamMediaVideo);
    }

    /**
     * 批量删除摄像头参数
     *
     * @param ids 需要删除的摄像头参数ID
     * @return 结果
     */
    @Override
    public int deleteBusStreamMediaVideoByIds(Long[] ids) {
        return busStreamMediaVideoMapper.deleteBusStreamMediaVideoByIds(ids);
    }

    /**
     * 删除摄像头参数信息
     *
     * @param id 摄像头参数ID
     * @return 结果
     */
    @Override
    public int deleteBusStreamMediaVideoById(Long id) {
        return busStreamMediaVideoMapper.deleteBusStreamMediaVideoById(id);
    }
}

Controller层:

import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.domain.BusStreamMediaVideo;
import com.ruoyi.system.domain.BusStreamMediaVideoParamDto;
import com.ruoyi.system.service.IBusStreamMediaVideoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/streamMediaVideo")
public class BusStreamMediaVideoController extends BaseController
{
    @Autowired
    private IBusStreamMediaVideoService busStreamMediaVideoService;

    /**
     * 查询摄像头参数列表
     */
    @GetMapping("/getVideoInfo")
    public TableDataInfo list(BusStreamMediaVideo busStreamMediaVideo)
    {
        startPage();
        List<BusStreamMediaVideo> list = busStreamMediaVideoService.selectBusStreamMediaVideoList(busStreamMediaVideo);
        return getDataTable(list);
    }

    /**
     * 设置流媒体服务器参数
     */
    @PostMapping("/setStreamMediaParam")
    public AjaxResult setStreamMediaParam(BusStreamMediaVideoParamDto busStreamMediaVideoDto)
    {
        return busStreamMediaVideoService.setStreamMediaParam(busStreamMediaVideoDto);
    }

    /**
     * 查询流媒体服务器参数
     */
    @GetMapping("/getStreamMediaParam")
    public BusStreamMediaVideoParamDto getStreamMediaParam()
    {
        return busStreamMediaVideoService.getStreamMediaParam();
    }


    /**
     * 获取摄像头参数详细信息
     */
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id)
    {
        return AjaxResult.success(busStreamMediaVideoService.selectBusStreamMediaVideoById(id));
    }

    /**
     * 新增摄像头参数
     */
    @PostMapping
    public AjaxResult add(BusStreamMediaVideo busStreamMediaVideo)
    {
        return toAjax(busStreamMediaVideoService.insertBusStreamMediaVideo(busStreamMediaVideo));
    }

    /**
     * 修改摄像头参数
     */
    @PutMapping
    public AjaxResult edit(@RequestBody BusStreamMediaVideo busStreamMediaVideo)
    {
        return toAjax(busStreamMediaVideoService.updateBusStreamMediaVideo(busStreamMediaVideo));
    }

    /**
     * 删除摄像头参数
     */
 @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable Long[] ids)
    {
        return toAjax(busStreamMediaVideoService.deleteBusStreamMediaVideoByIds(ids));
    }
}

注意上面流媒体参数设置和获取接口直接存储进redis中。

前缀使用常量类存储:

public class RedisPTConstants {
    public static final String STREAMMEDIAPARAM = "streamMediaParam:";
}

3、前端代码实现

主页面代码streamVideo.vue:

<template>
  <div class="app-container">
    <el-form
      :model="queryParams"
      ref="queryForm"
      :inline="true"
      v-show="showSearch"
      label-width="68px"
    >
      <el-form-item label="名称" prop="name">
        <el-input
          v-model="queryParams.name"
          placeholder="请输入名称"
          clearable
          size="small"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"
          >搜索</el-button
        >
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['runcontrolmange:streamMediaVideo:add']"
          >新增</el-button
        >
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="el-icon-edit"
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['runcontrolmange:streamMediaVideo:edit']"
          >修改</el-button
        >
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['runcontrolmange:streamMediaVideo:remove']"
          >删除</el-button
        >
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="el-icon-setting"
          size="mini"
          :disabled="single"
          @click="videoChange"
          >预览</el-button
        >
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-edit"
          size="mini"
          @click="handleSetting"
          v-hasPermi="['runcontrolmange:streamMediaVideo:set']"
          >设置</el-button
        >
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-edit"
          size="mini"
          @click="handleIllustrate"
          >说明</el-button
        >
      </el-col>
    </el-row>

    <el-table
      v-loading="loading"
      :data="videoList"
      @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column
        show-overflow-tooltip
        label="摄像头名称"
        align="center"
        prop="name"
      />
      <el-table-column
        show-overflow-tooltip
        label="rtsp视频流Url"
        align="center"
        prop="rtspStreamAddress"
      />
      <el-table-column
        show-overflow-tooltip
        label="摄像头坐标"
        align="center"
        prop="areaName"
      />
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['runcontrolmange:streamMediaVideo:edit']"
            >修改</el-button
          >
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['runcontrolmange:streamMediaVideo:remove']"
            >删除</el-button
          >
        </template>
      </el-table-column>
    </el-table>

    <pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />

    <!-- 添加或修改识别用户对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
        <el-form-item label="摄像头名称" prop="name">
          <el-input v-model="form.name" placeholder="请输入摄像头名称" />
        </el-form-item>

        <el-form-item label="rtsp视频流Url" prop="rtspStreamAddress">
          <el-input v-model="form.rtspStreamAddress" placeholder="请输入rtsp视频流Url" />
        </el-form-item>

        <el-form-item label="摄像头位置" prop="coordinate">
          <el-input
            v-model="lightPoint"
            placeholder="点击新增/更改摄像头坐标"
            @focus="onMap"
          />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>

    <!-- 流媒体服务设置对话框 -->
    <el-dialog title="流媒体服务设置" :visible.sync="setVisible" width="500px" append-to-body>
      <el-form ref="setForm" :model="setForm" :rules="rules1" label-width="120px">
        <el-form-item label="IP" prop="ip">
          <el-input v-model="setForm.ip" placeholder="请输入IP" />
        </el-form-item>

        <el-form-item label="端口" prop="port">
          <el-input v-model.number="setForm.port" placeholder="请输入端口号" />
        </el-form-item>

        <el-form-item label="密钥" prop="secret">
          <el-input type="password" v-model="setForm.secret" placeholder="请输入密钥" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitSetForm">确 定</el-button>
        <el-button @click="() => {this.setVisible = false;}">取 消</el-button>
      </div>
    </el-dialog>
    <el-dialog title="rtsp地址规则说明" :visible.sync="illVisible" width="50%" append-to-body>
      <div style="margin: 20px 40px 40px 40px; height: 60vh; overflow-y: scroll;">
        大华<br/>
        大华摄像机RTSP地址规则为:rtsp://[username]:[password]@[ip]:[port]/cam/realmonitor?channel=1&subtype=0<br/>
        说明:<br/>
        username: 用户名。例如admin。<br/>
        password: 密码。例如admin123。<br/>
        ip: 为设备IP。例如 192.168.1.101。<br/>
        port: 端口号默认为554,若为默认可不填写。<br/>
        channel: 通道号,起始为1。例如通道2,则为channel=2。<br/>
        subtype: 码流类型,主码流为0(即subtype=0),辅码流为1(即subtype=1)。<br/>
        如:rtsp://admin:admin123@192.168.1.101/cam/realmonitor?channel=1&subtype=1<br/>
        <br/>
        海康<br/>
        海康摄像机RTSP地址规则为:rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream<br/>
        说明:<br/>
        username: 用户名。例如admin。<br/>
        password: 密码。例如admin123。<br/>
        ip: 为设备IP。例如192.168.1.104。<br/>
        port: 端口号默认为554,若为默认可不填写。<br/>
        codec:有h264、MPEG-4、mpeg4这几种。<br/>
        channel: 通道号,起始为1。例如通道1,则为ch1。<br/>
        subtype: 码流类型,主码流为main,辅码流为sub。<br/>
        如:rtsp://admin:admin123@192.168.1.104/h264/ch1/subtype/av_stream<br/>
        <br/>
        宇视<br/>
        宇视摄像机RTSP地址规则为:rtsp://[username]:[password]@[ip]:[port]/media/video1/2/3<br/>
        说明:<br/>
        username: 用户名。例如admin。<br/>
        password: 密码。例如admin123。<br/>
        ip: 为设备IP。例如 192.168.1.107。<br/>
        port: 端口号默认为554,若为默认可不填写。<br/>
        video: 1代表主码流、2辅码流、3第三码流<br/>
        如:rtsp://admin:admin123@192.168.1.107/media/video2<br/>
        <br/>
        华为<br/>
        华为摄像机RTSP地址规则为:rtsp://[username]:[password]@[ip]:[port]/LiveMedia/[channel]/Media1/2<br/>
        说明:<br/>
        username: 用户名。例如admin。<br/>
        password: 密码。例如admin123。<br/>
        ip: 为设备IP。例如192.168.1.110。<br/>
        port: 端口号默认为554,若为默认可不填写。<br/>
        channel: 通道号,起始为1。例如通道1,则为ch1。
        Media:1代表主码流、2辅码流<br/>
        如:rtsp://admin:admin123@192.168.1.110/LiveMedia/ch1/Media2
      </div>
    </el-dialog>
    <!-- 摄像头预览 -->
    <preVideo ref="video" :videoOpen="videoOpen" @close="() => {this.videoOpen = false;this.previewUrl = ''}" :url="previewUrl" />
    <!-- 地图中显示摄像头坐标 -->
    <videoMap ref="videoMap" @childEvent="parentEvent"></videoMap>
  </div>
</template>

<script>
import {
  addVideo,
  updateVideo,
  delVideo,
  getVideoInfo,
  setStreamData,
  getStreamData,
} from "@/api/system/streamVideo";
import preVideo from "./previedVideo.vue";
import videoMap from "./videoMap";
import axios from 'axios'

export default {
  name: "carHkVideo",
  components: {
    preVideo,
    videoMap,
  },
  data() {
    return {
      // 遮罩层
      loading: false,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // 表格数据
      videoList: [],
      // 弹出层标题
      title: "",
      // 是否显示弹出层
      open: false,
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
      },
      // 表单参数
      form: {
        coordinate: "",
      },
      // 表单校验
      rules: {},
      rules1: {
        ip: [
          { required: true, trigger: "blur", message: "ip为必填项" },
          { pattern: /^(([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])$/, trigger: "blur", message: "必须输入合法的ip格式" },
        ],
        port: [
          {type: 'number', message: '端口号必须为数字'},
          {required: true, message: '端口号为必填项', trigger: 'blur'},
        ],
        secret: [
          {required: true, message: '密钥为必填项', trigger: 'blur'},
        ],
      },
      dialogVisible: false,
      rowList: {},
      // 预览摄像头的参数
      openVideoData: [{rtspStreamAddress:''}],
      lightPoint: [],
      setForm: {},
      setVisible: false,
      illVisible: false,
      videoOpen: false,
      previewUrl: '',
    };
  },
  created() {
    this.getList();
  },
  mounted() {},
  destroyed() {},
  methods: {
    videoChange() {
      this.videoOpen = true;
      getStreamData().then(res => {
        let {ip, port, secret} = res
        if(ip && port && secret){
          let {rtspStreamAddress} = Object.assign({}, this.openVideoData[0])
          let url = `http://${ip}:${port}/index/api/addStreamProxy?vhost=${ip}&app=live&stream=${new Date().getTime()}&url=${rtspStreamAddress}&secret=${secret}`
          axios({ url, method: "get", timeout: 5000}).then(({data}) => {
            if(data.code == 0) {
              let data1 = data.data.key
              let index = data1.indexOf('/')
              let ip = data1.substring(0, index)
              let urlPart = data1.substring(index + 1)
              let url = `http://${ip}:${port}/${urlPart}.flv`
              this.previewUrl = url
            } else this.$message.error('不可预览,请检查摄像头和流媒体服务的配置')
          }).catch(err => {
            console.log(err);
            this.$message.error(err + ',不可预览,请检查摄像头和流媒体服务的配置')
          })
        } else this.$message.error('预览前请先设置流媒体服务的IP、端口号和秘钥')
      })
    },
    handleSetting() {
      this.setVisible = true;
      getStreamData().then((res) => {
        this.setForm = Object.assign({}, res, {port: parseInt(res.port)})
      })
    },
    handleIllustrate() {
      this.illVisible = true;
    },
    parentEvent(data) {
      this.form.siteX = data[0];
      this.form.siteY = data[1];
      this.lightPoint = `${this.form.siteX},${this.form.siteY}`;
      this.form.coordinate = this.lightPoint;
    },
    onMap() {
      this.$refs.videoMap.dialogVisible = true;
      this.$refs.videoMap.init();
      this.$refs.videoMap.coordinate = this.form.coordinate;
      this.$refs.videoMap.drawPoint([this.form.siteX, this.form.siteY]);
    },
    getList() {
      getVideoInfo(this.queryParams).then((res) => {
        this.videoList = res.rows;
        this.total = res.total;
        this.loading = false;
      });
    },
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    handleAdd() {
      this.reset();
      this.open = true;
      this.lightPoint = "";
      this.title = "新增摄像头";
    },
    handleUpdate(row) {
      this.reset();
      this.title = "修改";
      this.open = true;
      let id;
      if (row.id) {
        id = row.id;
        this.videoList.forEach((item) => {
          if (item.id == id) {
            this.form = JSON.parse(JSON.stringify(item));
            this.lightPoint = item.areaName;
          }
        });
      } else {
        id = this.ids;
        this.videoList.forEach((item) => {
          if (item.id == id[0]) {
            this.form = JSON.parse(JSON.stringify(item));
            this.lightPoint = item.areaName;
          }
        });
      }
    },
    /** 提交按钮 */
    submitForm() {
      this.$refs["form"].validate((valid) => {
        if (valid) {
          if (this.form.id != null) {
            updateVideo(this.form).then((response) => {
              this.msgSuccess("修改成功");
              this.open = false;
              this.getList();
            });
          } else {
            addVideo(this.form).then((response) => {
              this.msgSuccess("新增成功");
              this.open = false;
              this.getList();
            });
          }
        }
      });
    },
    submitSetForm() {
      this.$refs["setForm"].validate((valid) => {
        if (valid) {
          this.$prompt('请输入二次密码校验权限', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            inputPattern: /^123$/,
            inputErrorMessage: '二次密码校验失败'
          }).then(({ value }) => {
            this.$message({
              type: 'success',
              message: '校验成功'
            });
            setStreamData(this.setForm).then(res => {
              if(res.code == 200) {
                this.$message.success('修改成功');
                this.setVisible = false;
              }
              else this.$message.error(res.msg);
            })
          }).catch(() => {
            this.$message({
              type: 'info',
              message: '取消修改'
            });       
          });
        }
      })
    },
    handleDelete(row) {
      const ids = row.id || this.ids;
      this.$confirm("是否确认删除该条数据项?", "警告", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(function () {
          return delVideo(ids);
        })
        .then(() => {
          this.getList();
          this.msgSuccess("删除成功");
        });
    },
    // 多选框选中数据
    handleSelectionChange(selection) {
      this.openVideoData = [];
      this.openVideoData = selection;
      this.ids = selection.map((item) => item.id);
      this.single = selection.length !== 1;
      this.multiple = !selection.length;
    },
    cancel() {
      this.open = false;
      this.reset();
    },
    reset() {
      this.form = {
        ip: null,
        name: null,
        username: null,
        password: null,
        port: null,
        coordinate: "",
      };

      this.resetForm("form");
    },
  },
};
</script>
<style scoped>
.dialog_div {
  width: 100%;
}
.show1 {
  width: 90%;
  height: 433px;
  margin: auto;
}
.plugin {
  width: 100%;
  height: 400px;
}

.my-tag {
  margin-left: 3px;
}

.my-group-btn {
  margin-top: 5px;
}
</style>
<style lang="scss">
</style>

4、实现在地图上点选和回显坐标组件videoMap.vue

<template>
  <div class="cont">
    <el-dialog
      title="选取摄像头位置"
      :visible.sync="dialogVisible"
      width="70%"
      :modal="false"
      v-loading="loading"
      :before-close="handleClose"
    >
      <div id="pMap" v-if="dialogVisible"></div>
      <p class="showPoint">经纬度:{{ coordinate }}</p>
      <div class="dialogfooter">
        <el-button type="primary" size="small" @click="submitForm">确 定</el-button>
        <el-button size="small" @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import Map from "ol/Map";
import View from "ol/View";
import Feature from "ol/Feature";
import { Point } from "ol/geom";
import { Icon, Style } from "ol/style";
import { Image as ImageLayer, Vector as VectorLayer, Tile as TileLayer } from "ol/layer";
import { Vector as VectorSource, ImageWMS } from "ol/source";

export default {
  data() {
    return {
      dialogVisible: false,
      loading: false,
      layers: null,
      map: null,
      zoom: null,
      lightLayer: null, 
      coordinate: null,
    };
  },
  mounted() {
    if (this.coordinate) {
      this.drawPoint(this.coordinate);
    }
  },
  watch: {},
  methods: {
    // 初始化地图
    init() {
      let self = this;
      self.$nextTick(() => {
        self.layers = new ImageLayer({
          extent: [911908.3769988124, 110617.87078181792,1596307.9757537232, 420506.5270969288], // 边界,
          source: new ImageWMS({
            url: "http://127.0.0.1:8000/geoserver/nyc/wms",
            // Layers需要指定要显示的图层名
            params: {
              LAYERS: "nyc:nyc_roads",
              exceptions: "application/vnd.ogc.se_inimage",
              FORMAT: "image/png",
            },
            serverType: "geoserver",
          }),
          
        });
        // 摄像头位置所放的图层
        this.lightLayer = new VectorLayer({
          source: new VectorSource({ features: [] }),
        });
        // 绘制地图线的图层
        this.map = new Map({
          layers: [this.layers,this.lightLayer],
          target: "pMap",
          view: new View({
           //地图中心点
            center: [987777.93778, 213834.81024],
            zoom: 12,
            maxZoom: 20,
            minZoom: 4,
          }),
        });
         this.onPoint();
      });
    },
    drawPoint(data, isTrue) {
      let url = "";
      url = "/images/video.png";
      this.$nextTick(() => {
        if (isTrue) {
          this.removePoint();
        }
        let feature = new Feature({
          // geometry 几何图形
          geometry: new Point([Number(data[0]), Number(data[1])]),
        });
        let style = new Style({
          image: new Icon({
            scale: 0.3,
            src: url,
            anchor: [0.48, 0.52],
          }),
        });
        feature.setStyle(style);
        this.lightLayer.getSource().addFeature(feature);
      });
    },
    removePoint() {
      let self = this;
      let allPointFeatures = self.lightLayer.getSource().getFeatures();
      allPointFeatures.forEach((item) => {
        self.lightLayer.getSource().removeFeature(item);
      });
    },
    onPoint() {
      // 监听singleclick事件
      let _this = this;
      this.map.on("singleclick", function (e) {
        _this.coordinate = e.coordinate;
        if (_this.coordinate) {
          _this.drawPoint(_this.coordinate, true);
        }
      });
    },

    handleClose() {
      this.dialogVisible = false;
    },
    submitForm() {
      this.$emit("childEvent", this.coordinate);
      this.dialogVisible = false;
    },
    cancel() {
      this.dialogVisible = false;
    },
  },
};
</script>
<style>
#pMap {
  width: 100%;
  height: 80vh;
}
.el-dialog__header {
  background-color: #409eff;
}
.el-dialog__title,
.el-dialog__close {
  color: #fff !important;
}
.el-dialog__body {
  padding: 5px;
}

.showPoint {
  position: absolute;
  top: 50px;
  color: #070707;
  z-index: 1;
  left: 50px;
}
.dialogfooter {
  position: absolute;
  bottom: 10px;
  right: 10px;
}
</style>

5、实现flv.js进行摄像头预览组件previedVideo.vue

<template>
  <div class="container" v-if="videoOpen">
    <!-- 摄像头 -->
    <VueDragResize v-if="videoOpen" class="drag" :w="800" :h="600" v-on:resizing="resize" v-on:dragging="resize">
        <!-- 摄像头 -->
        <div v-if="videoOpen" class="video-dialog">
            <div class="body-container">
                <img
                    class="close-video"
                    src="@/assets/video/close.png"
                    alt=""
                    @click="videoClose"
                />
                <div v-loading="loading" :element-loading-text="loadingText" style="width: 100%;height: 100%">
                    <video v-if="videoOpen" ref="video" id="video" class="video" muted></video>
                </div>
              </div>
          </div>
      </VueDragResize>
  </div>
  </template>
    
  <script>
  import flvjs from "flv.js/dist/flv.min.js";
  import VueDragResize from "vue-drag-resize";
  export default {
    name: 'preVideo',
    components: { VueDragResize },
    props: {
        videoOpen: {
            default: false,
        },
        url: {
            type: String,
            default: '',
        }
    },
    data() {
      return {
        options: { top: 0, left: 0, width: 0, height: 0 },
        flvPlayer: null,
        loading: true,
        loadingText: '加载中',
      };
    },
    watch: {
        url(newVal) {
            if(newVal == '') this.stopPlay();
            else this.$nextTick(() => {
                this.startPlay()
            })
        },
    },
    methods: {
      resize(newRect) {
        this.options.width = newRect.width;
        this.options.height = newRect.height;
        this.options.top = newRect.top;
        this.options.left = newRect.left;
      },
      startPlay() {
        this.loading = true;
        if (flvjs.isSupported()) {
            let videoElement = document.getElementById("video");
            this.flvPlayer = flvjs.createPlayer(
                {
                    type: "flv",
                    url: this.url,
                    hasAudio: false,
                    isLive: false,
                }
            );
            this.flvPlayer.attachMediaElement(videoElement);
            this.flvPlayer.load();
            this.flvPlayer.play();
            this.loading = false;
        } else {
            this.loading = false;
            this.$message.error("当前浏览器暂不支持flvjs视频流播放");
        }
      },
      stopPlay() {
        if (!this.flvPlayer) return;
        this.flvPlayer.pause(); //停止播放
        this.flvPlayer.unload(); //停止加载
        this.flvPlayer.detachMediaElement(); //销毁实例
        this.flvPlayer.destroy();
        this.flvPlayer = null;
        this.loading = true;
      },
      videoClose() {
        this.$emit('close')
      }
    },
  };
  </script>
    
<style lang="scss" scoped>
.container {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1001;
}
.drag {
  cursor: move;
  z-index: 1000;
}

.video-dialog {
  z-index: 1000;
    width: 100%;
    height: 100%;
    display: flex;
    background-color: #00000064;
    justify-content: center;
    align-items: center;
    background-image: url("~@/assets/video/bg.png");
    background-repeat: no-repeat;
    background-size: 100% 100%;
    -moz-background-size: 100% 100%;
    ::v-deep .el-dialog__header,
    .el-dialog__footer {
      display: none !important;
    }
    ::v-deep .el-dialog__body {
      padding: 20px 20px 10px 20px;
    }
  }
  ::v-deep .el-dialog {
    background-color: transparent !important;
    box-shadow: none;
    margin-top: 0px !important;
    background-image: url("../../assets/video/bg.png");
    background-repeat: no-repeat;
    background-size: 100% 100%;
    -moz-background-size: 100% 100%;
  }
  .body-container {
    position: relative;
    width: 100%;
    height: 100%;
  }
  .close-video {
    position: absolute;
    width: 60px;
    height: 55px;
    top: 10px;
    right: 10px;
    z-index: 999;
    &:hover {
      cursor: pointer;
      color: rgba(112, 165, 255, 0.517);
    }
  }
  .videoPlayer {
    margin: 10px;
    display: flex;
    justify-content: center;
    align-items: center;
    ::v-deep .video-js {
      width: 100%;
      height: 80vh;
      margin: 20px;
      // height: 90%;
    }
  }
  .close-video {
    position: absolute;
    width: 60px;
    height: 55px;
    top: 0px;
    right: 0px;
    z-index: 9999;
    &:hover {
      cursor: pointer;
    }
  }
  #video {
    width: 100%;
    height: 100%;
    padding: 20px;
    position: absolute;
    bottom: 0;
  }
  </style>

6、以上所需引入依赖

vue-drag-resize 可拖动缩放的组件

npm i -s vue-drag-resize

flv.js

npm install --save flv.js

可参考如下:

Nginx-http-flv-module流媒体服务器搭建+模拟推流+flv.js在前端html和Vue中播放HTTP-FLV视频流:

Nginx-http-flv-module流媒体服务器搭建+模拟推流+flv.js在前端html和Vue中播放HTTP-FLV视频流_霸道流氓气质的博客-CSDN博客

7、添加请求后台api的streamVideo.js

import request from '@/utils/request'

export function getVideoInfo(query) {
    return request({
        url: '/streamMediaVideo/getVideoInfo',
        method: 'get',
        params: query
    })
}
// 新增
export function addVideo(query) {
    return request({
        url: '/streamMediaVideo/',
        method: 'post',
        params: query
    })
}


// 修改
export function updateVideo(query) {
    return request({
        url: '/streamMediaVideo',
        method: 'put',
        data: query
    })
}

// 删除
export function delVideo(id) {
    return request({
        url: '/streamMediaVideo/' + id,
        method: 'delete'
    })
}

// 设置视频流服务数据get请求
export function setStreamData(query) {
    return request({
        url: '/streamMediaVideo/setStreamMediaParam',
        method: 'post',
        params: query
    })
}
// 设置视频流服务数据get请求
export function getStreamData(query) {
    return request({
        url: '/streamMediaVideo/getStreamMediaParam',
        method: 'get',
        params: query
    })
}

8、如何模拟一个rtsp的视频流

这里以海康威视摄像rtsp流地址模拟为例

Windows上使用FFmpeg实现本地视频推送模拟海康协议rtsp视频流:

Windows上使用FFmpeg实现本地视频推送模拟海康协议rtsp视频流_霸道流氓气质的博客-CSDN博客

其他厂家协议格式在页面上新增说明按钮,格式内容

大华

大华摄像机RTSP地址规则为:rtsp://[username]:[password]@[ip]:[port]/cam/realmonitor?channel=1&subtype=0
说明:

username: 用户名。例如admin。
password: 密码。例如admin123。
ip: 为设备IP。例如 192.168.1.101。
port: 端口号默认为554,若为默认可不填写。
channel: 通道号,起始为1。例如通道2,则为channel=2。
subtype: 码流类型,主码流为0(即subtype=0),辅码流为1(即subtype=1)。

如:rtsp://admin:admin123@192.168.1.101/cam/realmonitor?channel=1&subtype=1

海康

rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream

说明:
username: 用户名。例如admin。
password: 密码。例如admin123。
ip: 为设备IP。例如192.168.1.104。
port: 端口号默认为554,若为默认可不填写。
codec:有h264、MPEG-4、mpeg4这几种。
channel: 通道号,起始为1。例如通道1,则为ch1。
subtype: 码流类型,主码流为main,辅码流为sub。

如:rtsp://admin:admin123@192.168.1.104/h264/ch1/subtype/av_stream

宇视

rtsp://[username]:[password]@[ip]:[port]/media/video1/2/3

说明:
username: 用户名。例如admin。
password: 密码。例如admin123。
ip: 为设备IP。例如 192.168.1.107。
port: 端口号默认为554,若为默认可不填写。
video: 1代表主码流、2辅码流、3第三码流

如:rtsp://admin:admin123@192.168.1.107/media/video2

华为

rtsp://[username]:[password]@[ip]:[port]/LiveMedia/[channel]/Media1/2

说明:
username: 用户名。例如admin。
password: 密码。例如admin123。
ip: 为设备IP。例如192.168.1.110。
port: 端口号默认为554,若为默认可不填写。
channel: 通道号,起始为1。例如通道1,则为ch1。
Media:1代表主码流、2辅码流

如:rtsp://admin:admin123@192.168.1.110/LiveMedia/ch1/Media2

示例代码下载:

含数据库mysql、前后端代码、Zlmediakit在windows上编译后程序以及运行报错常用dll

https://download.csdn.net/download/BADAO_LIUMANG_QIZHI/89034337

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
对于Spring Boot整合ZLMediaKit,你可以按照以下步骤进行操作: 1. 首先,确保你已经正确安装了ZLMediaKit。可以参考官方文档进行安装:https://github.com/xiongziliang/ZLMediaKit/blob/master/README.zh.md 2. 在Spring Boot项目中添加ZLMediaKit的依赖。在项目的pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>com.github.xiongziliang</groupId> <artifactId>ZLMediaKit-Java</artifactId> <version>1.2.2</version> </dependency> ``` 3. 创建一个自定义的Spring Boot配置类,用于初始化ZLMediaKit相关的配置和组件。可以参考以下示例: ```java @Configuration public class ZLMediaKitConfig { @Autowired private ZLMediaKitProperties properties; @Bean(initMethod = "start", destroyMethod = "stop") public ZLMediaKit zlMediaKit() { ZLMediaKit zlMediaKit = new ZLMediaKit(); zlMediaKit.setConfig(properties.getConfig()); return zlMediaKit; } } ``` 其中,`ZLMediaKitProperties`是一个自定义的配置类,用于读取ZLMediaKit相关的配置信息。你可以根据自己的需求进行定义和配置。 4. 配置ZLMediaKit的相关参数。可以在application.properties或application.yml文件中添加以下配置项: ``` zlmediakit.config=【你的ZLMediaKit配置文件路径】 ``` 其中,`zlmediakit.config`是你的ZLMediaKit配置文件的路径,可以根据自己的实际情况进行配置。 5. 在需要使用ZLMediaKit的地方注入`ZLMediaKit`对象,并调用相应的方法进行操作。例如: ```java @RestController public class MediaController { @Autowired private ZLMediaKit zlMediaKit; @GetMapping("/startRecord") public String startRecord() { zlMediaKit.startRecord("rtmp://localhost/live/stream", "record.flv"); return "success"; } } ``` 在上述示例中,我们通过注入`ZLMediaKit`对象,调用`startRecord`方法来开始录制指定的。 以上就是整合Spring BootZLMediaKit的基本步骤,希望能对你有所帮助。如有更多问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霸道流氓气质

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值