panlinlin
2021-05-08 e48fa711a3664bece9b3e58840a75fe7c05bc47c
添加截图(快照)功能
7个文件已修改
231 ■■■■■ 已修改文件
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/config/index.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/Login.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/channelList.vue 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/test.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
@@ -12,7 +12,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.*;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.Map;
@@ -25,6 +25,8 @@
    @Autowired
    private MediaConfig mediaConfig;
    public interface RequestCallback{
        void run(JSONObject response);
@@ -94,6 +96,53 @@
        return responseJSON;
    }
    public void sendPostForImg(String api, Map<String, Object> param, String targetPath, String fileName) {
        OkHttpClient client = new OkHttpClient();
        String url = String.format("http://%s:%s/index/api/%s",  mediaConfig.getIp(), mediaConfig.getHttpPort(), api);
        JSONObject responseJSON = null;
        logger.debug(url);
        FormBody.Builder builder = new FormBody.Builder();
        builder.add("secret",mediaConfig.getSecret());
        if (param != null && param.keySet().size() > 0) {
            for (String key : param.keySet()){
                if (param.get(key) != null) {
                    builder.add(key, param.get(key).toString());
                }
            }
        }
        FormBody body = builder.build();
        Request request = new Request.Builder()
                .post(body)
                .url(url)
                .build();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                if (targetPath != null) {
                    File snapFolder = new File(targetPath);
                    if (!snapFolder.exists()) {
                        snapFolder.mkdirs();
                    }
                    File snapFile = new File(targetPath + "/" + fileName);
                    FileOutputStream outStream = new FileOutputStream(snapFile);
                    outStream.write(response.body().bytes());
                    outStream.close();
                }
            }
        } catch (ConnectException e) {
            logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
            logger.info("请检查media配置并确认ZLM已启动...");
        }catch (IOException e) {
            logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
        }
    }
    public JSONObject getMediaList(String app, String stream, String schema, RequestCallback callback){
        Map<String, Object> param = new HashMap<>();
@@ -201,4 +250,12 @@
        param.put("local_port", localPortSStr);
        sendPost("kick_sessions",param, null);
    }
    public void getSnap(String flvUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
        Map<String, Object> param = new HashMap<>();
        param.put("url", flvUrl);
        param.put("timeout_sec", timeout_sec);
        param.put("expire_sec", expire_sec);
        sendPostForImg("getSnap",param, targetPath, fileName);
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -15,6 +15,7 @@
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
import com.genersoft.iot.vmp.service.IMediaService;
import com.genersoft.iot.vmp.service.IPlayService;
@@ -23,14 +24,18 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Response;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.UUID;
@Service
@@ -82,8 +87,32 @@
            cmder.closeRTPServer(playResult.getDevice(), channelId);
            RequestMessage msg = new RequestMessage();
            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid());
            msg.setData("Timeout");
            WVPResult wvpResult = new WVPResult();
            wvpResult.setCode(-1);
            wvpResult.setMsg("Timeout");
            msg.setData(wvpResult);
            resultHolder.invokeResult(msg);
        });
        result.onCompletion(()->{
            // 点播结束时调用截图接口
            try {
                String path = ResourceUtils.getURL("classpath:").getPath()+"static/static/snap/";
                String fileName =  deviceId + "_" + channelId + ".jpg";
                ResponseEntity responseEntity =  (ResponseEntity)result.getResult();
                if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
                    WVPResult wvpResult = (WVPResult)responseEntity.getBody();
                    if (wvpResult.getCode() == 0) {
                        StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
                        String flvUrl = streamInfoForSuccess.getFlv();
                        // 请求截图
                        zlmresTfulUtils.getSnap(flvUrl, 5, 1, path, fileName);
                    }
                }
                System.out.println(path);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        });
        if (streamInfo == null) {
            // 发送点播消息
@@ -98,7 +127,10 @@
                msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                Response response = event.getResponse();
                cmder.closeRTPServer(playResult.getDevice(), channelId);
                msg.setData(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
                WVPResult wvpResult = new WVPResult();
                wvpResult.setCode(-1);
                wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
                msg.setData(wvpResult);
                resultHolder.invokeResult(msg);
                if (errorEvent != null) {
                    errorEvent.response(event);
@@ -109,7 +141,10 @@
            if (streamId == null) {
                RequestMessage msg = new RequestMessage();
                msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                msg.setData(String.format("点播失败, redis缓存streamId等于null"));
                WVPResult wvpResult = new WVPResult();
                wvpResult.setCode(-1);
                wvpResult.setMsg(String.format("点播失败, redis缓存streamId等于null"));
                msg.setData(wvpResult);
                resultHolder.invokeResult(msg);
                return playResult;
            }
@@ -117,7 +152,13 @@
            if (rtpInfo != null && rtpInfo.getBoolean("exist")) {
                RequestMessage msg = new RequestMessage();
                msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                msg.setData(JSON.toJSONString(streamInfo));
                WVPResult wvpResult = new WVPResult();
                wvpResult.setCode(0);
                wvpResult.setMsg("success");
                wvpResult.setData(streamInfo);
                msg.setData(wvpResult);
                resultHolder.invokeResult(msg);
                if (hookEvent != null) {
                    hookEvent.response(JSONObject.parseObject(JSON.toJSONString(streamInfo)));
@@ -133,7 +174,11 @@
                    RequestMessage msg = new RequestMessage();
                    msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                    Response response = event.getResponse();
                    msg.setData(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
                    WVPResult wvpResult = new WVPResult();
                    wvpResult.setCode(-1);
                    wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
                    msg.setData(wvpResult);
                    resultHolder.invokeResult(msg);
                });
            }
@@ -163,6 +208,13 @@
            streamInfo.setTransactionInfo(transactionInfo);
            redisCatchStorage.startPlay(streamInfo);
            msg.setData(JSON.toJSONString(streamInfo));
            WVPResult wvpResult = new WVPResult();
            wvpResult.setCode(0);
            wvpResult.setMsg("sucess");
            wvpResult.setData(streamInfo);
            msg.setData(wvpResult);
            resultHolder.invokeResult(msg);
        } else {
            logger.warn("设备预览API调用失败!");
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
@@ -19,6 +19,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -31,6 +32,7 @@
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.springframework.web.context.request.async.DeferredResult;
import java.io.FileNotFoundException;
import java.util.UUID;
import javax.sip.message.Response;
web_src/config/index.js
@@ -18,6 +18,13 @@
          '^/debug': '/'
        }
      },
      '/static/snap': {
        target: 'http://localhost:18080',
        changeOrigin: true,
        // pathRewrite: {
        //   '^/static/snap': '/static/snap'
        // }
      },
    },
web_src/src/components/Login.vue
@@ -80,7 +80,7 @@
      this.$axios({
          method: 'get',
    url:"/api/user/login",
          url:"/api/user/login",
        params: loginParam
      }).then(function (res) {
        console.log(JSON.stringify(res));
web_src/src/components/channelList.vue
@@ -30,9 +30,27 @@
            <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" border style="width: 100%">
                <el-table-column prop="channelId" label="通道编号" width="210">
                </el-table-column>
                <el-table-column prop="channelId" label="设备编号" width="210">
                <el-table-column prop="deviceId" label="设备编号" width="210">
                </el-table-column>
                <el-table-column prop="name" label="通道名称">
                </el-table-column>
                <el-table-column label="快照" width="80" align="center">
                  <template slot-scope="scope">
                    <img style="max-height: 3rem;max-width: 4rem;"
                         :id="scope.row.deviceId + '_' + scope.row.channelId"
                         :src="getSnap(scope.row)"
                         @error="getSnapErrorEvent($event.target.id)"
                         alt="">
<!--                    <el-image-->
<!--                      :id="'snapImg_' + scope.row.deviceId + '_' + scope.row.channelId"-->
<!--                      :src="getSnap(scope.row)"-->
<!--                      @error="getSnapErrorEvent($event, scope.row)"-->
<!--                      :fit="'contain'">-->
<!--                      <div slot="error" class="image-slot">-->
<!--                        <i class="el-icon-picture-outline"></i>-->
<!--                      </div>-->
<!--                    </el-image>-->
                  </template>
                </el-table-column>
                <el-table-column prop="subCount" label="子节点数">
                </el-table-column>
@@ -100,7 +118,8 @@
            total: 0,
            beforeUrl: "/deviceList",
            isLoging: false,
            autoList: true
            autoList: true,
            loadSnap:{}
        };
    },
@@ -122,7 +141,6 @@
            } else {
                this.showSubchannels();
            }
        },
        initParam: function () {
            this.deviceId = this.$route.params.deviceId;
@@ -174,8 +192,6 @@
            }).catch(function (error) {
                console.log(error);
            });
        },
        //通知设备上传媒体流
@@ -190,18 +206,22 @@
                method: 'get',
                url: '/api/play/start/' + deviceId + '/' + channelId
            }).then(function (res) {
                console.log(res.data)
                let streamId = res.data.streamId;
                that.isLoging = false;
                if (!!streamId) {
                    // that.$refs.devicePlayer.play(res.data, deviceId, channelId, itemData.hasAudio);
                    that.$refs.devicePlayer.openDialog("media", deviceId, channelId, {
                        streamInfo: res.data,
                        hasAudio: itemData.hasAudio
                    });
                    that.initData();
                } else {
                    that.$message.error(res.data);
                if (res.data.code == 0) {
                  setTimeout(()=>{
                    console.log("下载截图")
                    let snapId = deviceId + "_" + channelId;
                    that.loadSnap[snapId] = 0;
                    that.getSnapErrorEvent(snapId)
                  },5000)
                  that.$refs.devicePlayer.openDialog("media", deviceId, channelId, {
                    streamInfo: res.data.data,
                    hasAudio: itemData.hasAudio
                  });
                  that.initData();
                }else {
                  that.$message.error(res.data.msg);
                }
            }).catch(function (e) {});
        },
@@ -228,7 +248,24 @@
              }
            });
        },
        getSnap: function (row){
          return '/static/snap/' + row.deviceId + '_' + row.channelId + '.jpg'
        },
        getSnapErrorEvent: function (id){
          if (typeof (this.loadSnap[id]) != "undefined") {
            console.log("下载截图" + this.loadSnap[id])
            if (this.loadSnap[id] > 5) {
              delete this.loadSnap[id];
              return;
            }
            setTimeout(()=>{
              this.loadSnap[id] ++
              document.getElementById(id).setAttribute("src", '/static/snap/' + id + '.jpg?' + new Date().getTime())
            },1000)
          }
        },
        showDevice: function () {
            this.$router.push(this.beforeUrl).then(() => {
                this.initParam();
web_src/src/components/test.vue
@@ -24,6 +24,9 @@
          <div v-for="index of timeNode" class="timeQuery-label-cell" :style="'left:' + (100.0/timeNode*index).toFixed(4) + '%'">
            <div class="timeQuery-label-cell-label">{{24/timeNode * index}}</div>
          </div>
          <ul>
            <li v-for="item of allDataList" >{{!!item.name?item.name:item.dname}}</li>
          </ul>
        </div>
      </el-col>
    </el-row>
@@ -36,6 +39,7 @@
  name: "test",
  data() {
    return {
      allDataList:[],
      timeNode: 24,
      recordData:[
        {
@@ -58,6 +62,32 @@
    };
  },
  mounted() {
    var list1 = [{
                  key: Math.random()*10,
                  name: "人1"
                },{
                  key: Math.random()*10,
                  name: "人2"
                },{
                  key: Math.random()*10,
                  name: "人3"
                }]
    var list2 = [{
              key: Math.random()*10,
              dname: "部门1"
              },{
              key: Math.random()*10,
              dname: "部门2"
              },{
              key: Math.random()*10,
              dname: "部门3"
              }]
    var allData = list1.concat(list2)
    allData.sort((a, b)=>{
      return a.key-b.key;
    })
    this.allDataList = allData;
    for (let i = 1; i <= 24; i++) {
      console.log("<div class=\"timeQuery-label-cell\" style=\"left: " + (100.0/24*i).toFixed(4) + "%\"></div>")
    }