javacv拉取rtsp流通過websocket傳輸到web前端顯示
說明一下,我這裏只是介紹一下如何實現的一個小demo,因爲我做的這個rtsp解析主要是一個測試工具,簡單說一下需求,其他的服務器爲我提供了了一個rtsp流,我要做的就是將這個視頻流解析在web端進行展示,我的這個需求很簡單,只是作爲一個測試工具來說,並不是商業版本,所以爲了追求效率,並且也不會遇到什麼高併發的情況,只是提供這樣一種思路做出快速開發滿足於業務需求。其實這就相當於直播的感覺,因爲我獲取的rtsp流都是通過監控攝像頭獲取的實時數據,所以我這邊是要去解析rtsp流的,至於爲什麼是rtsp,就要問提供流的爲什麼是rtsp呢?爲了滿足需求,想過幾種方案,純前端直接解析rtsp,但是需要組件,而且查詢資料後發現這些插件好像並不好使,不是這個不兼容就這那個不兼容,要不是就是需要瀏覽器去安裝插件,太麻煩。後端解析,前端顯示呢,大部分的方案都說是將rtsp轉換成rtmp流,然後通過前端組件,比如video.js這種去重新解析,還要通過nginx去搭建服務武器,太麻煩,我最不想要的就是搭建各種環境,實際條件也不允許這麼麻煩還要搭建環境,最快捷的方式就是寫的這個項目,最好直接就能跑起來,所以,我就想到了視頻反正都是一幀一幀的圖片,不如前端就直接不停的刷新圖片得了,這不就是視頻了麼,再說一下,我這個是監控視頻顯示,對音頻沒要求,所以我的處理是沒處理音頻的,這裏只是做視頻顯示,說了這麼多,下面開始正題吧。項目地址:https://github.com/001ItSky/videoservice
1、首先我們需要引入相關的jar包,javacv和websocket的,pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.de</groupId>
<artifactId>videoservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>videoservice</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<commons.io.version>2.5</commons.io.version>
<commons.fileupload.version>1.3.3</commons.fileupload.version>
<hutool.version>4.6.4</hutool.version>
<fastjson.version>1.2.47</fastjson.version>
<lang3.version>3.9</lang3.version>
<jsckson.version>2.10.3</jsckson.version>
<javacv.version>1.5.1</javacv.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<!--文件上傳工具類 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons.fileupload.version}</version>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--好用的工具集-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${lang3.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>${javacv.version}</version>
<type>pom</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、需要配置websocket,開啓websocket支持
package com.de.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
*
* * @projectName videoservice
* * @title WebSocketConfig
* * @package com.de.config
* * @description 開啓websocket支持
* * @author IT_CREAT
* * @date 2020 2020/4/12 0012 下午 16:39
* * @version V1.0.0
*
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3、編寫websocket服務
注意,因爲websocket用的是tomcat的,websoket發送的數據有三種數據類型,string類型,bytebuffer類型,object類型,但是說白了其實就只有兩種,也就是string和bytebuffer,對象是要在底層經行編碼轉換的,所以,對於對象而言我們先要寫個編碼器,不然到時候發送的時候會報解析失敗的錯誤。
1)存放圖片數據的實體,因爲我們要想前端發送數據的是圖片數據
package com.de.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
*
* * @projectName videoservice
* * @title Image
* * @package com.de.entity
* * @description 存放圖片數據的實體
* * @author IT_CREAT
* * @date 2020 2020/4/18 0018 下午 22:54
* * @version V1.0.0
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Image {
private byte[] imageByte;
}
2)轉嗎器(轉碼器的作用就是將數據轉成json字符串返回),發送時websocket會自行調用,這裏要用到了AjaxResult,這是自己寫的同前端交互傳輸的統一對象。後面會貼出來
package com.de.entity;
import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.ArrayUtils;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
/**
*
* * @projectName videoservice
* * @title ImageEncoder
* * @package com.de.entity
* * @description websocket 傳輸對象轉碼
* * @author IT_CREAT
* * @date 2020 2020/4/18 0018 下午 22:53
* * @version V1.0.0
*
*/
public class ImageEncoder implements Encoder.Text<Image> {
@Override
public String encode(Image image) throws EncodeException {
if(image != null && !ArrayUtils.isEmpty(image.getImageByte())){
String base64Image= Base64.encode(image.getImageByte());
return JSON.toJSONString(new AjaxResult(AjaxResult.Type.SUCCESS_IMG_BYTE,"獲取視頻幀成功",base64Image));
}
return JSON.toJSONString(AjaxResult.error("獲取視頻幀失敗"));
}
@Override
public void init(EndpointConfig endpointConfig) {
}
@Override
public void destroy() {
}
}
3)websocket服務編寫,記得一定要指明添加編碼器,ServerEndpoint註解的encoders 屬性指明
package com.de.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.de.entity.AjaxResult;
import com.de.entity.ImageEncoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* * @projectName videoservice
* * @title WebSocketServer
* * @package com.de.service
* * @description websocket服務
* * @author IT_CREAT
* * @date 2020 2020/4/12 0012 下午 16:41
* * @version V1.0.0
*
*/
@ServerEndpoint(value = "/webSocketService", encoders = {ImageEncoder.class})
@Component
@Slf4j
public class WebSocketServer {
/**
* 靜態變量,用來記錄當前在線連接數。應該把它設計成線程安全的。
*/
private static int onlineCount = 0;
/**
* concurrent包的線程安全Set,用來存放每個客戶端對應的MyWebSocket對象。
*/
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**
* 與某個客戶端的連接會話,需要通過它來給客戶端發送數據
*/
private Session session;
/**
* 接收userId
*/
private String userId = "";
/**
* 連接建立成功調用的方法
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
this.userId = session.getId();
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
//加入set中
} else {
webSocketMap.put(userId, this);
//加入set中
addOnlineCount();
//在線數加1
}
log.info("用戶連接:" + userId + ",當前在線人數爲:" + getOnlineCount());
HashMap<String, String> data = new HashMap<>();
data.put("userId", userId);
sendMessageByStr(JSON.toJSONString(AjaxResult.success("連接成功", data)));
}
/**
* 連接關閉調用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
//從set中刪除
subOnlineCount();
}
log.info("用戶退出:" + userId + ",當前在線人數爲:" + getOnlineCount());
}
/**
* 收到客戶端消息後調用的方法
*
* @param message 客戶端發送過來的消息,必須是json串
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用戶消息:" + userId + ",報文:" + message);
//可以羣發消息
//消息保存到數據庫、redis
if (StringUtils.isNotBlank(message)) {
//解析發送的報文
JSONObject jsonObject = JSON.parseObject(message);
//追加發送人(防止串改)
if (jsonObject != null) {
jsonObject.put("fromUserId", this.userId);
String toUserId = jsonObject.getString("toUserId");
//傳送給對應toUserId用戶的websocket
if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
webSocketMap.get(toUserId).sendMessageByStr(jsonObject.toJSONString());
} else {
log.error("請求的userId:" + toUserId + "不在該服務器上");
//否則不在這個服務器上,發送到mysql或者redis
}
}
}
}
/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用戶錯誤:" + this.userId + ",原因:" + error.getMessage());
log.error("websocket error: ", error);
}
public void sendMessageByStr(String message) {
if (StringUtils.isNotBlank(message)) {
try {
if (this.session.isOpen()) {
this.session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
log.error("發送到用戶:" + this.userId + "信息失敗 ,信息是:" + message);
log.error("websocket send str msg exception: ", e);
}
}
}
public void sendMessageByObject(Object message) {
if (message != null) {
try {
this.session.getBasicRemote().sendObject(message);
} catch (IOException | EncodeException e) {
log.error("發送到用戶:" + this.userId + "信息失敗 ,信息是:" + message);
log.error("websocket send object msg exception: ", e);
}
}
}
public void sendBinary(ByteBuffer message) {
if (message != null) {
try {
this.session.getBasicRemote().sendBinary(message);
} catch (IOException e) {
log.error("發送到用戶:" + this.userId + "信息失敗 ,信息是:" + message);
log.error("websocket send byteBuffer msg exception: ", e);
}
}
}
/**
* 發送自定義消息
*/
public static void sendInfo(String message, String userId) {
log.info("發送消息到:" + userId + ",報文:" + message);
if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessageByStr(message);
} else {
log.error("用戶" + userId + ",不在線!");
}
}
/**
* 向所有的客戶端發送消息
*
* @param byteBuffer byteBuffer
* @throws IOException IOException
*/
public static void sendAllByBinary(ByteBuffer byteBuffer) {
if (!webSocketMap.isEmpty()) {
Collection<WebSocketServer> values = webSocketMap.values();
for (WebSocketServer next : values) {
next.sendBinary(byteBuffer);
}
}
}
public static void sendAllByObject(Object message) {
if (!webSocketMap.isEmpty()) {
Collection<WebSocketServer> values = webSocketMap.values();
for (WebSocketServer next : values) {
next.sendMessageByObject(message);
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
4)AjaxResult,前面用了,說了貼出來
package com.de.entity;
import org.apache.commons.lang3.ObjectUtils;
import java.util.HashMap;
/**
* 操作消息提醒
*
* @author ruoyi
*/
public class AjaxResult extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
/**
* 狀態碼
*/
public static final String CODE_TAG = "code";
/**
* 返回內容
*/
public static final String MSG_TAG = "msg";
/**
* 數據對象
*/
public static final String DATA_TAG = "data";
/**
* 狀態類型
*/
public enum Type {
/**
* 成功
*/
SUCCESS(0),
SUCCESS_IMG_BYTE(201),
/**
* 警告
*/
WARN(301),
/**
* 錯誤
*/
ERROR(500);
private final int value;
Type(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
/**
* 初始化一個新創建的 AjaxResult 對象,使其表示一個空消息。
*/
public AjaxResult() {
}
/**
* 初始化一個新創建的 AjaxResult 對象
*
* @param type 狀態類型
* @param msg 返回內容
*/
public AjaxResult(Type type, String msg) {
super.put(CODE_TAG, type.value);
super.put(MSG_TAG, msg);
}
/**
* 初始化一個新創建的 AjaxResult 對象
*
* @param type 狀態類型
* @param msg 返回內容
* @param data 數據對象
*/
public AjaxResult(Type type, String msg, Object data) {
super.put(CODE_TAG, type.value);
super.put(MSG_TAG, msg);
if (ObjectUtils.isNotEmpty(data)) {
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static AjaxResult success() {
return AjaxResult.success("操作成功");
}
/**
* 返回成功數據
*
* @return 成功消息
*/
public static AjaxResult success(Object data) {
return AjaxResult.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回內容
* @return 成功消息
*/
public static AjaxResult success(String msg) {
return AjaxResult.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回內容
* @param data 數據對象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data) {
return new AjaxResult(Type.SUCCESS, msg, data);
}
/**
* 返回警告消息
*
* @param msg 返回內容
* @return 警告消息
*/
public static AjaxResult warn(String msg) {
return AjaxResult.warn(msg, null);
}
/**
* 返回警告消息
*
* @param msg 返回內容
* @param data 數據對象
* @return 警告消息
*/
public static AjaxResult warn(String msg, Object data) {
return new AjaxResult(Type.WARN, msg, data);
}
/**
* 返回錯誤消息
*
* @return
*/
public static AjaxResult error() {
return AjaxResult.error("操作失敗");
}
/**
* 返回錯誤消息
*
* @param msg 返回內容
* @return 警告消息
*/
public static AjaxResult error(String msg) {
return AjaxResult.error(msg, null);
}
/**
* 返回錯誤消息
*
* @param msg 返回內容
* @param data 數據對象
* @return 警告消息
*/
public static AjaxResult error(String msg, Object data) {
return new AjaxResult(Type.ERROR, msg, data);
}
}
這樣整個websocket服務就大功告成了。然後就是解析rtsp流了。
4、解析rtsp,通過websokect發送到前端顯示
1) application.yml配置(主要):爲什麼我的url只是個視頻地址呢,因爲我主要是測試,你這裏寫rtsp地址也行,javacv會根據地址不同去解析,如何搭建一個快速的rtsp服務器做測試呢,我後面會說
rtsp:
url: E:/視頻/VID_20141122_212032.mp4
transport:
type: udp
完整的application.yml
# 開發環境配置
server:
# 服務器的HTTP端口,默認爲80
port: 8080
servlet:
# 應用的訪問路徑
context-path: /
tomcat:
# tomcat的URI編碼
uri-encoding: UTF-8
# tomcat最大線程數,默認爲200
max-threads: 800
# Tomcat啓動初始化的線程數,默認值25
min-spare-threads: 30
# 日誌配置
logging:
file:
name: delogs.log
# Spring配置
spring:
# 模板引擎
thymeleaf:
mode: HTML
encoding: utf-8
# 禁用緩存
cache: false
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
serialization:
FAIL_ON_EMPTY_BEANS: false
# profiles:
# active: druid
# 文件上傳
servlet:
multipart:
# 單個文件大小
max-file-size: 10MB
# 設置總上傳的文件大小
max-request-size: 20MB
# 服務模塊
devtools:
restart:
# 熱部署開關
enabled: true
rtsp:
url: E:/視頻/VID_20141122_212032.mp4
transport:
type: udp
2) 解析rtsp服務,websocket發送數據到前端
package com.de.rtsp;
import com.de.entity.Image;
import com.de.service.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
*
* * @projectName videoservice
* * @title MediaUtils
* * @package com.de.rtsp
* * @description 獲取rtsp流,解析爲視頻幀,websocket傳遞到前臺顯示
* * @author IT_CREAT
* * @date 2020 2020/4/12 0012 下午 18:24
* * @version V1.0.0
*
*/
@Slf4j
@Component
@EnableAsync
public class MediaTransfer {
@Value("${rtsp.url}")
private String rtspUrl;
@Value("${rtsp.transport.type}")
private String rtspTransportType;
private static FFmpegFrameGrabber grabber;
private static boolean isStart = false;
/**
* 視頻幀率
*/
public static int frameRate = 24;
/**
* 視頻寬度
*/
public static int frameWidth = 480;
/**
* 視頻高度
*/
public static int frameHeight = 270;
/**
* 開啓獲取rtsp流,通過websocket傳輸數據
*/
@Async
public void live() {
log.info("連接rtsp:"+rtspUrl+",開始創建grabber");
grabber = createGrabber(rtspUrl);
if (grabber != null) {
log.info("創建grabber成功");
} else {
log.info("創建grabber失敗");
}
startCameraPush();
}
/**
* 構造視頻抓取器
*
* @param rtsp 拉流地址
* @return
*/
public FFmpegFrameGrabber createGrabber(String rtsp) {
// 獲取視頻源
try {
FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(rtsp);
grabber.setOption("rtsp_transport", rtspTransportType);
//設置幀率
grabber.setFrameRate(frameRate);
//設置獲取的視頻寬度
grabber.setImageWidth(frameWidth);
//設置獲取的視頻高度
grabber.setImageHeight(frameHeight);
//設置視頻bit率
grabber.setVideoBitrate(2000000);
return grabber;
} catch (FrameGrabber.Exception e) {
log.error("創建解析rtsp FFmpegFrameGrabber 失敗");
log.error("create rtsp FFmpegFrameGrabber exception: ", e);
return null;
}
}
/**
* 推送圖片(攝像機直播)
*/
public void startCameraPush() {
Java2DFrameConverter java2DFrameConverter = new Java2DFrameConverter();
while (true) {
if (grabber == null) {
log.info("重試連接rtsp:"+rtspUrl+",開始創建grabber");
grabber = createGrabber(rtspUrl);
log.info("創建grabber成功");
}
try {
if (grabber != null && !isStart) {
grabber.start();
isStart = true;
log.info("啓動grabber成功");
}
if (grabber != null) {
Frame frame = grabber.grabImage();
if (null == frame) {
continue;
}
BufferedImage bufferedImage = java2DFrameConverter.getBufferedImage(frame);
byte[] bytes = imageToBytes(bufferedImage, "jpg");
//使用websocket發送視頻幀數據
WebSocketServer.sendAllByObject(new Image(bytes));
}
} catch (FrameGrabber.Exception | RuntimeException e) {
log.error("因爲異常,grabber關閉,rtsp連接斷開,嘗試重新連接");
log.error("exception : " , e);
if (grabber != null) {
try {
grabber.stop();
} catch (FrameGrabber.Exception ex) {
log.error("grabber stop exception: ", ex);
} finally {
grabber = null;
isStart = false;
}
}
}
}
}
/**
* 圖片轉字節數組
*
* @param bImage 圖片數據
* @param format 格式
* @return 圖片字節碼
*/
private byte[] imageToBytes(BufferedImage bImage, String format) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
ImageIO.write(bImage, format, out);
} catch (IOException e) {
log.error("bufferImage 轉 byte 數組異常");
log.error("bufferImage transfer byte[] exception: ", e);
return null;
}
return out.toByteArray();
}
}
3)rtsp解析服務啓動實體類(可以採用多線程啓動,我這裏是用的異步的方式,上面的服務代碼用了異步@Async,其實底層也是多線程,@PostConstruct註解的作用是項目啓動即加載,只加載一次,相當與spring的ben配置的初始化加載方法,至於爲什麼異步,因爲我這裏初始化加載,不採用異步,就會一直卡在那,因爲上面使用的是死循環監聽rtsp流,進行拉流操作,所以不異步項目都起不來,會一直卡在死循環處)
package com.de.rtsp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
*
* * @projectName videoservice
* * @title MadiaStart
* * @package com.de.rtsp
* * @description 程序啓動時加載一次,rtspj解析傳輸到websocket啓動類
* * @author IT_CREAT
* * @date 2020 2020/4/18 0018 下午 22:48
* * @version V1.0.0
*
*/
@Component
public class MediaStart {
@Autowired
MediaTransfer mediaTransfer;
@PostConstruct
public void init() {
//異步加載,因爲初始化時執行,live裏面是死循環監聽rtsp,如果不異步操作,就會卡死在初始化階段,項目就會起不來
mediaTransfer.live();
}
}
5、前端接受數據顯示
1)首先得有websocket的插件或者是處理方法,我這裏貼出來Websocket.js
;!(function (window) {
"use strict";
let Event = {
wsMesEvent: function (message) {
console.log(message)
}
}, dftOpt = {
protocol: (window.location.protocol == 'http:') ? 'ws://' : 'wss://'
, host: window.location.host
, port: '80'
, path: ''
, isReConect: false
, wsMesEvent: Event.wsMesEvent
}, Util = {
arrayLike(arrayLike) {
Array.from(arrayLike)
},
isArray(arr) {
Array.isArray(arr)
},
forEach(array, iterate) {
let index = -1
, length = array.length;
if (typeof iterate != 'function') {
return array;
}
while (++index < length) {
iterate.call(array, array[index], index);
}
},
isPlainObject(obj) {
let flag = false;
if (!obj || typeof obj != 'object') {
return flag;
}
if (obj.constructor.prototype.hasOwnProperty("isPrototypeOf")) {
flag = true;
}
return flag;
},
extend(...args) {
if (args.length <= 0) {
return
};
let target = args[0];
if (args.length == 1) {
return args[0]
};
this.forEach(args, (arg, i) => {
if (i != 0) {
var keys = Object.keys(arg);
this.forEach(keys, (key, i) => {
var val = arg[key];
if (this.isPlainObject(val) || this.isArray(val)) {
var newTarget = this.isArray(val) ? [] : {};
target[key] = this.extend(newTarget, val);
} else {
target[key] = val;
}
});
}
});
return target;
}
}, Ws = function (opt) {
//如果瀏覽器不支持websocket,直接退出
if (!this.isSupportWs()) {
alert("對不起,您的瀏覽器在不支持WebSocket,請先升級您的瀏覽器!!");
return;
}
let config = this.config = Util.extend({}, dftOpt, opt);
//接口地址url
this.url = config.host === "" || config.host === "" ?
config.protocol + config.path:
config.protocol + config.host + ':' + config.port + config.path;
//心跳狀態 爲false時不能執行操作 等待重連
this.isHeartBeat = false;
//重連狀態 避免不間斷的重連操作
this.isReconnect = config.isReConect;
//發送的消息
this.curSendMes = null;
//響應的信息
this.message = null;
//創建webSocket
this.ws;
//初始化websocket
this.initWs = function () {
//創建WebSocket
let ws = this.ws = new WebSocket(this.url);
// ws.binaryType = "arraybuffer";
//Ws連接函數:服務器連接成功
ws.onopen = (e) => {
console.log(`與${this.config.host}:${this.config.port}${this.config.path}連接已建立...`)
this.isHeartBeat = true;
//發佈事件
this.send();
};
//Ws消息接收函數:服務器向前端推送消息時觸發
ws.onmessage = (e) => {
//處理各種推送消
this.message = e.data;
config.wsMesEvent.apply(this, [e.data]);
}
//Ws異常事件:Ws報錯後觸發
ws.onerror = (e) => {
this.isHeartBeat = false;
this.reConnect();
}
//Ws關閉事件:Ws連接關閉後觸發
ws.onclose = (e) => {
console.log('連接已關閉...');
alert("websocket連接已關閉,按F5嘗試重新刷新頁面");
this.isHeartBeat = false;
ws = null;
this.reConnect();
};
};
this.initWs();
};
//判斷是否支持WebSocket
Ws.prototype.isSupportWs = function () {
return (window.WebSocket || window.MozWebSocket) ? true : false;
}
//重新連接
Ws.prototype.reConnect = function () {
//不需要重新連接,直接返回
if (!this.isReconnect) return;
this.isReconnect = true;
//沒連接上 會一直重連,設置延遲避免請求過多
setTimeout(() => {
this.initWs()
this.isReconnect = false;
}, 5000);
}
//發送消息
Ws.prototype.send = function (content) {
this.curSendMes = content || this.curSendMes;
if(this.curSendMes == null){
return;
}
if (this.isHeartBeat) {
// this.ws.send(bytes);
this.ws.send(this.curSendMes);
}
}
window.Ws = Ws;
})(window);
/***
* 使用方式:
* //建立連接
* var ws1 = new Ws({
* host:'123.207.167.163'
* ,port:9010
* ,path:'/ajaxchattest'
* ,wsMesEvent:function(message){
* //將接收到的二進制數據轉爲字符串
var unit8Arr = new Uint8Array(event.data) ;
* console.log(message)
* }
* });
* //發送請求
* ws1.send("111");
*
* //建立連接
* var ws2 = new Ws({
* host:'123.207.167.163'
* ,port:9010
* ,path:'/ajaxchattest'
* ,wsMesEvent:function(message){
* console.log(message)
* }
* });
* //發送請求
* ws2.send("222");
* */
2)前端頁面(我用的是Thymeleaf模板,整個展示的核心就兩個js, jquery.js和上面給出的websocket.js)
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('視頻展示rtsp')"/>
</head>
<body class="gray-bg">
<div style="padding: 20px">
<p style="font-size: 20px;color: #0a7491;font-weight: bold;font-family: 楷體">rtsp拉取視頻顯示</p>
<img id="show_video" src="">
</div>
<th:block th:include="include :: footer"/>
<script src="../static/js/workbench/WebSocket.js" th:src="@{/js/workbench/WebSocket.js}"></script>
<script th:inline="javascript">
var wsUrl = getWsPath() + "/webSocketService";
//建立連接
var ws1 = new Ws({
host: ""
, port: ""
, path: wsUrl
, wsMesEvent: function (message) {
//將接收到的圖片數據進行刷新顯示
var data = JSON.parse(message);
if (data.code === 0) {
console.log(message)
} else if (data.code === 201) {
$("#show_video").attr("src", "data:image/*;base64," + data.data)
}
}
});
</script>
</body>
</html>
整個流程就完了,效果如下:
rtsp服務器搭建,基於vlc播放器:https://blog.csdn.net/IT_CREATE/article/details/105626071