文档说明

  • 此文档基于「大华 DSS-C 」平台进行视频对接
  • 主要分为两个层面的对接
    • 后端数据对接
    • 前端展示对接
  • 参考《石景山项目》

参考资料

准备工作

  1. 访问视频云需要IP、端口、用户名、密码。其中IP、用户名、密码由 提供,这个用户名和密码也能用于视频云的

    Web管理界面和客户端登录

  2. 如需使用WEB端调起PC客户端插件 播放/回放视频 需要安装 General_DSS-LightWeight-Client.exe

    双击打开一直点下一步操作即可完成安装

  3. 调用客户端插件的前端 「DHWs.js」 下载地址

后端数据对接

工具类

VideoUtil.java

  • 登录视频平台「 Token 保存到 Redis 」
  • Token 自动保活/手动保活
  • 获取组织根节点信息「组织不可播放」
  • 根据组织编码获取设备信息「设备不可播放,这里相当于硬盘录像机」
  • 根据组织编码获取通道信息「通道可播放,这里相当于摄像头」
  • 根据通道 ID 和 协议类型 获取 视频流地址 「协议类型支持RTSP、FLV_HTTP、HLS三种,默认RTSP。HLS为m3u8格式」
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
package com.bjtcrj.gms.common.utils.video;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bjtcrj.gms.common.utils.RedisCacheManager;
import com.bjtcrj.gms.common.utils.video.model.VideoChannel;
import com.bjtcrj.gms.common.utils.video.model.VideoDevice;
import com.bjtcrj.gms.common.utils.video.model.VideoOrg;
import com.bjtcrj.gms.common.utils.video.vo.FirstLoginResult;
import com.bjtcrj.gms.common.utils.video.vo.SecondLoginResult;
import com.bjtcrj.gms.common.utils.video.vo.VideoDeviceChannel;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class VideoUtil {

private static final Logger logger = LoggerFactory.getLogger(VideoUtil.class);
/**
* 常量的写法
* // 用户名
* private static final String userName = "username";
*
* // 密码
* private static final String password = "password";
*
* // DSS-C 的 IP
* private static final String videoIp = "10.10.0.127";
*
* // 端口号
* private static final String videoPort = "8281";
*
* // 登录的接口
* private static final String loginUrl = "/videoService/accounts/authorize";
*
* // 获取组织的接口
* private static final String orgUrl = "/videoService/devicesManager/deviceTree";
*
* // 保活的接口
* private static final String keepAliveUrl = "/videoService/accounts/token/keepalive";
*
* // 获取视频URL的接口
* private static final String realMonitorUri = "/videoService/realmonitor/uri";
*
*/

/**
* 从配置文件获取
*/
@Value("#{configProperties['video.userName']}")
private String userName;

@Value("#{configProperties['video.password']}")
private String password;

@Value("#{configProperties['video.videoIp']}")
private String videoIp;

@Value("#{configProperties['video.videoPort']}")
private String videoPort;

@Value("#{configProperties['video.url.loginUrl']}")
private String loginUrl;

@Value("#{configProperties['video.url.orgUrl']}")
private String orgUrl;

@Value("#{configProperties['video.url.keepAliveUrl']}")
private String keepAliveUrl;

@Value("#{configProperties['video.url.realMonitorUri']}")
private String realMonitorUri;


// 加密方式
private static final String MD5 = "MD5";

// 登录客户端类型
public static final String CLIENT_TYPE = "winpc";

// 设备类型
public static final int DEVICE_CODE = 2;

// 通道类型
public static final int CHANNEL_CODE = 3;

// Token存到Redis定义的Key
private static final String VIDEO_KEY = "VIDEO_TOKEN";

// http
private static final String HTTP = "http://";

// https
private static final String HTTPS = "https://";

// 冒号(拼接用)
private static final String MH = ":";

// 是否开启保活(登录完成后会改为true)
private static boolean keep = false;

@Autowired
public RedisCacheManager redisCacheManager;

/**
* 实现两次登录并将 Token 放到 Redis
*/
public SecondLoginResult login() {
String loginUri = HTTP + videoIp + MH + videoPort + loginUrl;
FirstLoginResult firstLoginResult = VideoUtil.firstLogin(loginUri, userName);
if (!ObjectUtils.isEmpty(firstLoginResult)) {
SecondLoginResult secondLoginResult = VideoUtil.secondLogin(firstLoginResult, userName, password, loginUri);
assert secondLoginResult != null;
redisCacheManager.set(VIDEO_KEY, secondLoginResult.getToken(), secondLoginResult.getDuration());
VideoUtil.keep = true;
keepHeart();
return secondLoginResult;
}
return null;
}

/**
* 返回最新的Token
*
* @return
*/
public String getToken() {
String token = String.valueOf(redisCacheManager.get(VIDEO_KEY));
if (ObjectUtils.isEmpty(token) || "null".equals(token)) {
login();
token = String.valueOf(redisCacheManager.get(VIDEO_KEY));
}
return token;
}

/**
* 定时器,定时保活 10秒执行一次
*/
@Scheduled(cron = "*/10 * * * * ?")
public void pretreatment() {
if (keep) {
keepHeart();
}
}

/**
* 手动保活token并返回结果
*
* @return
*/
public HashMap<String, Object> freshenToken() {
HashMap<String, Object> map = new HashMap<>();
String token = getToken();
String keepAliveUri = HTTP + videoIp + MH + videoPort + keepAliveUrl;
boolean b = false;

JSONObject params = new JSONObject();
params.put("token", token);
Map<String, String> headers = new HashMap<>();
headers.put("X-Subject-Token", token);
try {
HttpClientResult httpClientResult = HttpRequest.doPut(keepAliveUri, headers, params.toJSONString());
String content = httpClientResult.getContent();
JSONObject jsonObject = JSON.parseObject(content);
Integer code = jsonObject.getInteger("code");
if (HttpConstant.SUCCESS_CODE.equals(code)) {
b = true;
}
} catch (Exception e) {
logger.error(e.getMessage());
e.printStackTrace();
}

map.put("接口调用结果:", b);
map.put("token:", token);
if (!b) {
getToken();
} else {
redisCacheManager.expire(VIDEO_KEY, 120);
}
return map;
}

/**
* 获取根结点
*
* @return
*/
public List<VideoOrg> getRootVideoOrg() {
String token = getToken();
String orgUri = HTTP + videoIp + MH + videoPort + orgUrl;
Map<String, Object> reqOrgParams = getReqOrgParams("", "01");

Map<String, String> headers = new HashMap<>();
headers.put("X-Subject-Token", token);
try {
HttpClientResult httpClientResult = HttpRequest.doGet(orgUri, headers, reqOrgParams);
String content = httpClientResult.getContent();
JSONObject jsonObject = JSON.parseObject(content);
JSONArray results = jsonObject.getJSONArray("results");
return JSONObject.parseArray(results.toJSONString(), VideoOrg.class);
} catch (Exception e) {
logger.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 根据orgId获取下级机构
*
* @param orgId
* @return
*/
public List<VideoOrg> getVideoOrg(String orgId) {
String token = getToken();
String orgUri = HTTP + videoIp + MH + videoPort + orgUrl;
Map<String, Object> reqOrgParams = getReqOrgParams(orgId, "01");

Map<String, String> headers = new HashMap<>();
headers.put("X-Subject-Token", token);
try {
HttpClientResult httpClientResult = HttpRequest.doGet(orgUri, headers, reqOrgParams);
String content = httpClientResult.getContent();
JSONObject jsonObject = JSON.parseObject(content);
JSONArray results = jsonObject.getJSONArray("results");
return JSONObject.parseArray(results.toJSONString(), VideoOrg.class);
} catch (Exception e) {
logger.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* 获取录像机设备(硬盘录像机不能直接播放)
*
* @param orgId
* @return
*/
public List<VideoDevice> reqVideoDevice(String orgId) {
String token = getToken();
String orgUri = "http://" + videoIp + ":" + videoPort + orgUrl;
Map<String, Object> reqOrgParams = getReqOrgParams(orgId, "01;1;ALL");
List<VideoDeviceChannel> orgChannelOrDevice = VideoUtil.getOrgChannelOrDevice(orgUri, token, reqOrgParams);
return orgChannelOrDevice.stream().map(VideoDeviceChannel::getVideoDevice).collect(Collectors.toList());
}

/**
* 获取视频通道(摄像机可直接播放)
*
* @param orgId
* @return
*/
public List<VideoChannel> reqVideoChannel(String orgId) {
String token = getToken();
String orgUri = "http://" + videoIp + ":" + videoPort + orgUrl;
Map<String, Object> reqOrgParams = getReqOrgParams(orgId, "01;0;ALL;ALL");

List<VideoDeviceChannel> orgChannelOrDevice = VideoUtil.getOrgChannelOrDevice(orgUri, token, reqOrgParams);

return orgChannelOrDevice.stream().map(VideoDeviceChannel::getVideoChannel).collect(Collectors.toList());
}

/**
* 根据通道ID及视频格式获取URL(HLS为m3u8格式)
*
* @param channelId 通道ID
* @param scheme 类型string,选填。协议类型,支持RTSP、FLV_HTTP、HLS三种,默认RTSP
* @return
*/
public String reqRealMonitorUri(String channelId, String scheme) {
String url = HTTP + videoIp + MH + videoPort + realMonitorUri;
String token = getToken();
Map<String, Object> params = new HashMap<>();
params.put("channelId", channelId);
params.put("subType", 0);
params.put("scheme", scheme);

Map<String, String> headers = new HashMap<>();
headers.put("X-Subject-Token", token);
try {
HttpClientResult httpClientResult = HttpRequest.doGet(url, headers, params);
String content = httpClientResult.getContent();
JSONObject contentJson = JSON.parseObject(content);
return contentJson.getString("url");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

/**
* (通用)获取组织的参数封装
*
* @param id
* @param typeCode
* @return
*/
public Map<String, Object> getReqOrgParams(String id, String typeCode) {
Map<String, Object> params = new HashMap<>();
params.put("id", id);
params.put("nodeType", 1);
params.put("typeCode", typeCode);
params.put("page", 1);
params.put("pageSize", 1000);
return params;
}

/**
* (通用)首次登录
*
* @param url
* @param userName
* @return
*/
public static FirstLoginResult firstLogin(String url, String userName) {
JSONObject paramJson = new JSONObject();
paramJson.put("userName", userName);
paramJson.put("clientType", CLIENT_TYPE);
try {
String result = HttpRequest.sendJsonPost(url, paramJson.toJSONString());
return JSON.parseObject(result, FirstLoginResult.class);
} catch (Exception e) {
logger.error(e.getMessage());
e.printStackTrace();
}
return null;
}

/**
* (通用)第二次登录
*
* @param firstLoginResult
* @param userName
* @param password
* @param url
* @return
*/
public static SecondLoginResult secondLogin(FirstLoginResult firstLoginResult, String userName, String password, String url) {
JSONObject paramJson = new JSONObject();
paramJson.put("userName", userName);
String method = firstLoginResult.getMethod();
String generalSign = "";
String encryptType = firstLoginResult.getEncryptType();
String randomKey = firstLoginResult.getRandomKey();
if (ObjectUtils.isEmpty(method)) {
generalSign = getGeneralSign(userName, password, firstLoginResult.getRealm(), randomKey, encryptType);
}
paramJson.put("signature", generalSign);
paramJson.put("randomKey", randomKey);
paramJson.put("encryptType", encryptType);
paramJson.put("clientType", CLIENT_TYPE);
try {
String result = HttpRequest.sendJsonPost(url, paramJson.toJSONString());
return JSON.parseObject(result, SecondLoginResult.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

/**
* (通用)调用保活接口
*/
public void keepHeart() {
String token = getToken();
String keepAliveUri = "http://" + videoIp + ":" + videoPort + keepAliveUrl;

boolean b = false;
JSONObject params = new JSONObject();
params.put("token", token);
Map<String, String> headers = new HashMap<>();
headers.put("X-Subject-Token", token);
try {
HttpClientResult httpClientResult = HttpRequest.doPut(keepAliveUri, headers, params.toJSONString());
String content = httpClientResult.getContent();
JSONObject jsonObject = JSON.parseObject(content);
Integer code = jsonObject.getInteger("code");
if (HttpConstant.SUCCESS_CODE.equals(code)) {
b = true;
}
} catch (Exception e) {
logger.error(e.getMessage());
e.printStackTrace();
}

if (!b) {
getToken();
} else {
redisCacheManager.expire(VIDEO_KEY, 120);
}
}

/**
* (通用)获取设备(硬盘录像机不能直接播放的)或者通道(监控探头可直接播放)
*
* @param url
* @param token
* @param params
* @return
*/
public static List<VideoDeviceChannel> getOrgChannelOrDevice(String url, String token, Map<String, Object> params) {
List<VideoDeviceChannel> videoDeviceChannels = new ArrayList<>();
Map<String, String> headers = new HashMap<>();
headers.put("X-Subject-Token", token);
try {
HttpClientResult httpClientResult = HttpRequest.doGet(url, headers, params);
String content = httpClientResult.getContent();
JSONObject contentJson = JSON.parseObject(content);
JSONArray results = contentJson.getJSONArray("results");
if (!ObjectUtils.isEmpty(results)) {
for (int i = 0; i < results.size(); i++) {
JSONObject jsonObject = results.getJSONObject(i);
int nodeType = jsonObject.getIntValue("nodeType");
VideoDeviceChannel videoDeviceChannel = new VideoDeviceChannel();
if (DEVICE_CODE == nodeType) {
VideoDevice videoDevice = JSON.parseObject(jsonObject.toJSONString(), VideoDevice.class);
videoDeviceChannel.setVideoDevice(videoDevice);
} else if (CHANNEL_CODE == nodeType) {
VideoChannel videoChannel = JSON.parseObject(jsonObject.toJSONString(), VideoChannel.class);
videoDeviceChannel.setVideoChannel(videoChannel);
}
videoDeviceChannels.add(videoDeviceChannel);
}
}
} catch (Exception e) {
logger.error(e.getMessage());
e.printStackTrace();
}
return videoDeviceChannels;
}

/**
* (通用)下面是计算签名用到的工具
*
* @param userName
* @param password
* @param realm
* @param randomKey
* @param encryptType
* @return
*/
private static String getGeneralSign(String userName, String password, String realm, String randomKey, String encryptType) {
String signature = encrypt(password, "MD5");
signature = encrypt(userName + signature, "MD5");
signature = encrypt(signature, "MD5");
signature = encrypt(userName + ":" + realm + ":" + signature, "MD5");
signature = encrypt(signature + ":" + randomKey, "MD5");
return signature;
}

private static String getSimpleSign(String userName, String password, String realm, String randomKey) {
String signature = encrypt(userName + ":" + realm + ":" + password, "MD5");
signature = encrypt(signature + ":" + randomKey, "MD5");
return signature;
}

private static String encrypt(String text, String method) {
if (MD5.equals(method)) {
return DigestUtils.md5Hex(text);
}
return null;
}
}

HttpClientResult.java

封装httpClient响应结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.bjtcrj.gms.common.utils.video;

import java.io.Serializable;

/**
* Description: 封装httpClient响应结果
*
* @author JourWon
* @date Created on 2018年4月19日
*/
public class HttpClientResult implements Serializable {

private static final long serialVersionUID = 2168152194164783950L;

/**
* 响应状态码
*/
private int code;

/**
* 响应数据
*/
private String content;

public HttpClientResult() {
}

public HttpClientResult(int code) {
this.code = code;
}

public HttpClientResult(String content) {
this.content = content;
}

public HttpClientResult(int code, String content) {
this.code = code;
this.content = content;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

@Override
public String toString() {
return "HttpClientResult [code=" + code + ", content=" + content + "]";
}

}

HttpConstant.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.bjtcrj.gms.common.utils.video;

/**
* @author Administrator
*/
public class HttpConstant {

public static final Integer SUCCESS_CODE = 200;

/**
* 更新密码原密码错误代码
*/
public static final int PASSWORD_ERROR_CODE = -2;


}

HttpRequest.java

发送http请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package com.bjtcrj.gms.common.utils.video;
import com.bjtcrj.gms.common.utils.video.HttpClientResult;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;
import java.util.Set;

/**
* 发送http请求
* @author Administrator
*/
public class HttpRequest {

private static final Logger logger = LoggerFactory.getLogger(HttpRequest.class);

// 编码格式。发送编码格式统一用UTF-8
private static final String ENCODING = "UTF-8";

// 设置连接超时时间,单位毫秒。
private static final int CONNECT_TIMEOUT = 600000;

// 请求获取数据的超时时间(即响应时间),单位毫秒。
private static final int SOCKET_TIMEOUT = 600000;

/**
* 包的头
* Description: 封装请求头
*
* @param params 参数个数
* @param httpMethod http方法
*/
public static void packageHeader(Map<String, String> params, HttpRequestBase httpMethod) {
// 封装请求头
if (params != null) {
Set<Map.Entry<String, String>> entrySet = params.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
// 设置到请求头到HttpRequestBase对象中
httpMethod.setHeader(entry.getKey(), entry.getValue());
}
}
}

private static RequestConfig getRequestConfig() {
return RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
}

/**
* 发送put请求;带请求参数
*
* @param url 请求地址
* @param headers 头
* @return {@link HttpClientResult}
* @throws Exception 异常
*/
public static HttpClientResult doPut(String url, Map<String, String> headers, String jsonStr) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPut httpPut = new HttpPut(url);
RequestConfig requestConfig = getRequestConfig();
httpPut.setConfig(requestConfig);
packageHeader(headers, httpPut);
httpPut.addHeader("Content-Type", "application/json;charset=UTF-8");
httpPut.setEntity(new StringEntity(jsonStr, "UTF-8"));
try {
return getHttpClientResult(httpClient, httpPut);
} finally {
release(null, httpClient);
}
}

/**
* 获取http客户端结果
* Description: 获得响应结果
*
* @param httpClient http客户端
* @param httpMethod http方法
* @return {@link HttpClientResult}
* @throws Exception 异常
*/
public static HttpClientResult getHttpClientResult(CloseableHttpClient httpClient, HttpRequestBase httpMethod) throws Exception {
// 执行请求
CloseableHttpResponse httpResponse = httpClient.execute(httpMethod);

// 获取返回结果
if (httpResponse != null && httpResponse.getStatusLine() != null) {
String content = "";
if (httpResponse.getEntity() != null) {
content = EntityUtils.toString(httpResponse.getEntity(), ENCODING);
}
return new HttpClientResult(httpResponse.getStatusLine().getStatusCode(), content);
}
return new HttpClientResult(HttpStatus.SC_INTERNAL_SERVER_ERROR);
}

/**
* 释放
* Description: 释放资源
*
* @param httpResponse http响应
* @param httpClient http客户端
* @throws IOException ioexception
*/
public static void release(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient) throws IOException {
// 释放资源
if (httpResponse != null) {
httpResponse.close();
}
if (httpClient != null) {
httpClient.close();
}
}


/**
* 发送get请求;带请求头和请求参数
*
* @param url 请求地址
* @param headers 请求头集合
* @param params 请求参数集合
* @return {@link HttpClientResult}
* @throws Exception 异常
*/
public static HttpClientResult doGet(String url, Map<String, String> headers, Map<String, Object> params) throws Exception {
// 创建httpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
// 创建访问的地址
URIBuilder uriBuilder = new URIBuilder(url);
if (params != null) {
Set<Map.Entry<String, Object>> entrySet = params.entrySet();
for (Map.Entry<String, Object> entry : entrySet) {
uriBuilder.setParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
}

// 创建http对象
HttpGet httpGet = new HttpGet(uriBuilder.build());
logger.info("URI:" + httpGet.getURI());
/*
setConnectTimeout:设置连接超时时间,单位毫秒。
setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection
超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。
setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。
*/
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
httpGet.setConfig(requestConfig);

// 设置请求头
packageHeader(headers, httpGet);

try {
// 执行请求并获得响应结果
return getHttpClientResult(httpClient, httpGet);
} finally {
// 释放资源
release(null, httpClient);
}
}

/**
* 向指定 URL 发送POST方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
StringBuilder result = new StringBuilder();
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result.toString();
}

public static String sendJsonPost(String url, String JSONBody) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(600000)
.setSocketTimeout(600000).setConnectTimeout(600000).build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
httpPost.setConfig(requestConfig);
httpPost.addHeader("Content-Type", "application/json;charset=UTF-8");
System.out.println("paramJson -- " + JSONBody);
httpPost.setEntity(new StringEntity(JSONBody, "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
String responseContent = EntityUtils.toString(entity, "UTF-8");
response.close();
httpClient.close();
return responseContent;
}


}

Result.java

返回结果类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package com.bjtcrj.gms.common.utils.video;

import java.io.Serializable;

/**
* 接口返回值
*
* @param <T>
* @author ubdi
*/
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
protected int code;
protected String message;
protected T data;

public int getCode() {
return this.code;
}

public void setCode(int code) {
this.code = code;
}

public String getMessage() {
return this.message;
}

public void setMessage(String message) {
this.message = message;
}

public T getData() {
return this.data;
}

public void setData(T data) {
this.data = data;
}

public Result(int code) {
this.code = code;
}

public Result(int code, T data) {
this.code = code;
this.data = data;
}

public Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}

public Result(int code, String message) {
this.code = code;
this.message = message;
}

public static <E> Result<E> success() {
return new Result<>(200);
}

public static <E> Result<E> success(String message) {
return new Result<>(200, message);
}

public static <E> Result<E> success(E data) {
return new Result<>(200, data);
}

public static <E> Result success(String message, E data) {
return new Result<>(200, message, data);
}

public static <E> Result<E> error(E data) {
return new Result<>(400, data);
}

public static <E> Result<E> error(String message, E data) {
return new Result<>(400, message, data);
}

public static <E> Result<E> error(String message) {
return new Result<>(400, message);
}

public static <E> Result<E> error(int code, String message) {
return new Result<>(code, message);
}

public Result() {
}
}

实体类

VideoChannel.java

视频通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package com.bjtcrj.gms.common.utils.video.model;

import lombok.Data;

@Data
public class VideoChannel {

// ID
private String id;

// 访问来源
private String accessSource;

// 编码通道功能,一个通道如果支持多种功能,用","分隔。编
private String cameraFunctions;

// 摄像头类型,"1":枪机,"2":球机,"3":半球等
private String cameraType;

// 设备大类,参考设备大类
private String category;

// 通道代码
private String channelCode;

// 通道ID
private String channelId;

// 通道名称
private String channelName;

// 通道序号,从0开始
private Integer channelSeq;

// 通道Sn
private String channelSn;

// 通道类型:"1",图片通道 "2",视频通道 "3",图片视频混合通道。
private String channelType;

// 设备标识。添加设备时产生的唯一标识符。
private String deviceCode;

// 级联域
private Boolean domain;

// 级联域ID
private Integer domainId;

// 完整检查(无用)
private Boolean fullCheck;

// 通道所在地图坐标的X值。
private String gpsX;

// 通道所在3d地图坐标的X值。
private String gpsX3d;

// 通道所在地图坐标的Y值。
private String gpsY;

// 通道所在3d地图坐标的Y值。
private String gpsY3d;

// 通道所在3d地图坐标的Z值。
private String gpsZ3d;

// 部分检查(无用)
private Boolean halfCheck;

// 图标
private String icon;

// 是否完整检查(无用)
private Boolean isFullCheck;

// 是否部分检查(无用)
private Boolean isHalfCheck;

// 是否在线
private Boolean isOnline;

// 是否为父节点
private Boolean isParent;

// 是否搜索(无意义)
private Boolean isSearched;

// 描述
private String memo;

// 3d描述(无意义)
private String memo3d;

// 模型角度 (无意义)
private String modelAngle;

// modelVangle(无意义)
private String modelVangle;

// name(无意义)
private String name;

// 节点类型(无意义)
private Integer nodeType;

// 不可用编码器通道
private Boolean notEnableEncoderChannel;

// 离线(无意义)
private String offlineReason;

// 在线(无意义)
private String online;

// 设备组织编码
private String orgCode;

// 是否上级(无意义)
private Boolean parent;

// 父节点ID
private String parentId;

// 父节点类型。1:设备组织,2:设备,3:通道。
private Integer parentNodeType;

// 投(无意义)
private String pitch;

// 资源code
private String resourceCode;

// 资源名称(无意义)
private String resourceName;

// sn(无意义)
private String sn;

// 排序
private Integer sort;

// 报警状态
private Integer stat;

// 状态
private Integer status;

// 小类编码
private Integer subCount;

// 设备小类
private String subType;

// 通道所属的单元类型
private String unitType;

// 人员抓拍点位Code
private String userChannelCode;

}

VideoDevice.java

视频设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package com.bjtcrj.gms.common.utils.video.model;

import lombok.Data;

import java.util.List;

/**
* 视频设备
*
* @author ubdi
* @date 2021/01/08
*/
@Data
public class VideoDevice {
/**
*访问资源
*/
private String accessSource;
/**
* 设备大类
*/
private String category;
/**
* 通道列表
*/
private List<String> channelList;
/**
* 子通道列表
*/
private List<String> children;
/**
* 设备序列号
*/
private String devSn;
/**
* 设备唯一标识
*/
private String deviceCode;
/**
* 设备编码
*/
private String deviceId;
/**
* 设备名称
*/
private String deviceName;
/**
* 级联域
*/
private boolean domain;
/**
* 级联域Id,字段为空或者0表示本级
*/
private int domainId;
/**
* (无意义)
*/
private boolean fullCheck;
/**
* (无意义)
*/
private boolean halfCheck;
/**
* 界面显示的图标名称
*/
private String icon;
/**
* 设备编码
*/
private String id;
/**
* 智能状态,0:非智能 1:智能
*/
private int intelliFlag;
/**
* 设备IP
*/
private String ip;
/**
*(无意义)
*/
private boolean isFullCheck;
/**
* (无意义)
*/
private boolean isHalfCheck;
/**
* 设备是否在线。
*/
private boolean isOnline;
/**
* 是否是父节点(是否有子节点),true:是父节点,false:不是父节点
*/
private boolean isParent;
/**
* 搜索(无意义)
*/
private boolean isSearched;
/**
* 参考厂商
*/
private String manufacturer;
/**
* 备忘录
*/
private String memo;
/**
* 节点名称
*/
private String name;
/**
* nodeType==3,通道字段
*/
private int nodeType;
/**
* 离线原因
*/
private String offlineReason;
/**
* 设备是否在线。"1":在线;"0":离线
*/
private String online;
/**
* 和parentId一样
*/
private String orgCode;
/**
* 组织名称
*/
private String orgName;
/**
* 组织类型,"1"为基本组织
*/
private String orgType;
/**
* 所属的paas编号,此值为paas基础平台所定义
*/
private String paasId;
/**
* 修改时间
*/
private boolean parent;
/**
* 所属组织的ID,如果属于root节点则为""
*/
private String parentId;
/**
* 资源code(无意义)
*/
private String resourceCode;
/**
* 资源名称(无意义)
*/
private String resourceName;
/**
* 组织自定义编码,通常用于国标编码
*/
private String sn;
/**
* 排序码
*/
private int sort;
/**
* 小类编码
*/
private int subCount;
/**
* 参考设备小类
*/
private String subType;
/**
* 修改时间
*/
private String updateTime;
}

VideoOrg.java

组织结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package com.bjtcrj.gms.common.utils.video.model;

import lombok.Data;

@Data
public class VideoOrg {
/**
* 组织编码,根组织为""
*/
private String id;

/**
* 级联域(无意义)
*/
private Boolean domain;

/**
* 级联域Id,字段为空或者0表示本级
*/
private Integer domainId;

/**
* 完整检查(无意义字段)
*/
private Boolean fullCheck;

/**
* 部分检查(无用字段)
*/
private Boolean halfCheck;

/**
* 界面显示的图标名称
*/
private String icon;

/**
* 无意义
*/
private Boolean isFullCheck;

/**
* 无意义
*/
private Boolean isHalfCheck;

/**
* 是否是父节点(是否有子节点),true:是父节点,false:不是父节点
*/
private Boolean isParent;

/**
* 组织编码
*/
private Boolean isSearched;

/**
* 备忘录
*/
private String memo;

/**
* 组织名称
*/
private String name;

/**
* 节点类型。1:组织
*/
private Integer nodeType;

/**
* org编码
*/
private String orgCode;

/**
* org名称
*/
private String orgName;

/**
* 组织自定义编码,通常用于国标编码
*/
private String orgSn;

/**
* 组织类型,"1"为基本组织
*/
private String orgType;

/**
* 组织编码
*/
private Boolean parent;

/**
* 父节点ID,如果父节点是root节点则为""
*/
private String parentId;

/**
* 无意义
*/
private Boolean root;

/**
* 组织自定义编码,通常用于国标编码
*/
private String sn;

/**
* 排序码
*/
private Integer sort;

/**
* 小类编码
*/
private Integer subCount;

/**
* 修改时间
*/
private String updateTime;
}

VO类

FirstLoginResult.java

第一次登录结果

1
package com.bjtcrj.gms.common.utils.video.vo;import lombok.Data;import java.io.Serializable;/** * 第一次登录结果 * @date 2021/01/04 */@Datapublic class FirstLoginResult implements Serializable {    /**     * 领域     */    private String realm;    /**     * 随机密钥     */    private String randomKey;    /**     * 加密类型     */    private String encryptType;    /**     * 方法     */    private String method;}

SecondLoginResult.java

第二次登录结果

1
package com.bjtcrj.gms.common.utils.video.vo;import lombok.Data;@Datapublic class SecondLoginResult {    /**     * 有效时间,单位秒     */    private int duration;    /**     * 令牌     */    private String token;    /**     * 用户名     */    private String userName;    /**     * 用户id     */    private String userId;    /**     * 最后一次登录Ip     */    private String lastLoginIp;}

VideoDeviceChannel.java

设备通道组合结果

1
package com.bjtcrj.gms.common.utils.video.vo;import com.bjtcrj.gms.common.utils.video.model.VideoChannel;import com.bjtcrj.gms.common.utils.video.model.VideoDevice;import lombok.Data;@Datapublic class VideoDeviceChannel {		  	// 通道    private VideoChannel videoChannel;		  	// 设备    private VideoDevice videoDevice;}

前端播放对接

使用客户端插件播放

引入「 DHWs.js 」

前端登录

通过后端获取 Redis 中的 Toke 进行前端登录

登录后可通过 ws 对象可调起 「轻量客户端插件」 进行播放

建议全局登录一次

1
// 大华登录const DHWsInstance = DHWs.getInstance(); // 获取对象实例,使用单例模式,唯一// token 方式登录时,userPwd 为空即可let ws = DHWsInstance;let loginIp = '10.10.0.127'; // 视频平台 IPlet loginPort = '8320' // 端口号 这里的端口号跟后端不一样可咨询let userName = 'username' // 用户名let token = 'xxxxxx'; // Tokenws.login({  loginIp: loginIp,  loginPort: loginPort,  userName: userName,  userPwd: '',  token: token});ws.on('loginState', (res) => {  if (res) {    console.log('登录成功');  } else {    console.log('登录失败');  }});window.onbeforeunload = function(){   console.log("tuichu");   this.ws.logout({      loginIp: '10.10.0.127'   });};

窗口播放

使用 「 ws.openVideo() 」 调用客户端插件进行弹窗播放

以下是窗口播放代码片段

1
// 播放let params = ['id1', 'id2'];// 填写通道IDws.openVideo(params);

窗口回放

使用 「 ws.openRecord() 」 调用客户端插件进行弹窗回放

以下是窗口回放代码片段

1
var param =    {      beginTime: '',// 开始时间      endTime: '',// 结束时间      channelId: '' // 通道ID    },    {      beginTime: '',// 开始时间      endTime: '',// 结束时间      channelId: '' // 通道ID    }}ws.openRecord(params);

固定位置播放/回放

控制 displayMode 参数可切换 播放/回放

1
const params = [    {        ctrlType: "playerWin",// 控件类型 playerWin 播放控件 ar AR控件        ctrlCode: "ctrl1",        ctrlProperty: {            displayMode: "1",// 播放模式 1 播放器预览模式,2 播放器回放模式            splitNum: 4,// 显示视频个数            channelList: [                {channelId: "11010702001320001373"},                {channelId: "11010702001320001150"},                {channelId: "11010702001320001437"},                {channelId: "11010704001320003497"}            ]        },        visible: true,// 展示、隐藏        posX: 1510,// x坐标        posY: 140,// y坐标        width: 400,// 控件宽度        height: 510,// 控件高度        ratio: 100,// 比例        scrollX: 0,//        scrollY: 0,        scrollXH: 0,        scrollYW: 0,        cutList: [            {                posX: 0,                posY: 0,                width: 0,                height: 0            }        ]    }];// 创建控件setTimeout(function(){   ws.createCtrl(params);   ws.on('createCtrlResult', (resctr) => {       console.log(resctr);   });}, 2000); //延迟2秒

使用video.js播放

获取通道m3u8播放地址

  1. 后端准备

根据工具类「 VideoUtil.java 」中封装的「 reqRealMonitorUri 」方法获取视频URL的接口

每次获取到的视频URL存在有效期,如果超过10分钟不查看视频,则此地址失效,要重新请求视频URL

  • @param channelId 通道ID
  • @param scheme 类型string,选填。协议类型,支持RTSP、FLV_HTTP、HLS三种,默认RTSP「这里使用HLS方式」
  • @return url 播放地址
  1. 前端准备

前端引入video.js

1
<link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet"><script src='https://vjs.zencdn.net/7.4.1/video.js'></script><script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-hls/5.15.0/videojs-contrib-hls.min.js" type="text/javascript"></script>
  1. 前端示例代码:
1
<!DOCTYPE html><html lang="zh-CN"><head>    <meta charset="UTF-8">    <title>前端播放m3u8格式视频</title>    <link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet">    <script src='https://vjs.zencdn.net/7.4.1/video.js'></script>    <script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-hls/5.15.0/videojs-contrib-hls.min.js" type="text/javascript"></script>    <!-- videojs-contrib-hls 用于在电脑端播放 如果只需手机播放可以不引入 --></head><body>    <style>        .video-js .vjs-tech {position: relative !important;}    </style>    <div>        <video id="myVideo" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto" data-setup='{}' style='width: 100%;height: auto'>            <source id="source" src="http://1252093142.vod2.myqcloud.com/4704461fvodcq1252093142/48c8a9475285890781000441992/playlist.m3u8" type="application/x-mpegURL"></source>        </video>    </div>    <div class="qiehuan" style="width:100px;height: 100px;background: red;margin:0 auto;line-height: 100px;color:#fff;text-align: center">切换视频</div></body> <script>    // videojs 简单使用    var myVideo = videojs('myVideo', {        bigPlayButton: true,        textTrackDisplay: false,        posterImage: false,        errorDisplay: false,    })    myVideo.play()    var changeVideo = function (vdoSrc) {        if (/\.m3u8$/.test(vdoSrc)) { //判断视频源是否是m3u8的格式            myVideo.src({                src: vdoSrc,                type: 'application/x-mpegURL' //在重新添加视频源的时候需要给新的type的值            })        } else {            myVideo.src(vdoSrc)        }        myVideo.load();        myVideo.play();    }    var src = 'http://1252093142.vod2.myqcloud.com/4704461fvodcq1252093142/f865d8a05285890787810776469/playlist.f3.m3u8';    document.querySelector('.qiehuan').addEventListener('click', function () {        changeVideo(src);    })</script>