1. 運(yùn)行結(jié)果
SpringBoot+WebSocket+WebRTC實(shí)現(xiàn)視頻通話
上述運(yùn)行結(jié)果中是有聲音(比較小而已)及動的畫面的(畫面不是靜止的)。
網(wǎng)上關(guān)于webrtc的文檔(文章)和視頻也挺多的,但是用SpringBoot結(jié)合WebRTC的卻屈指可數(shù),前一段時間小編我學(xué)習(xí)了一下WebRTC的相關(guān)知識,于是用SpringBoot+WebRTC實(shí)現(xiàn)了一個多人的線上自習(xí)室(有畫面,但是沒有聲音的那種,開啟聲音也挺簡單,在js代碼里設(shè)置一下即可[運(yùn)行結(jié)果在最后的總結(jié)里])。最近CSDN有活動,正好把前一段時間學(xué)習(xí)的知識運(yùn)用起來(下述代碼只是實(shí)現(xiàn)了,但是其中的邏輯是存在一定問題的,所以如果讀者用下述代碼,切記需要改動改動哈!)。既然是WebRTC,為什么又和WebSocket扯上關(guān)系了呢?因?yàn)槔肳ebSocket技術(shù)來發(fā)送消息具有實(shí)時性,你看我在這端發(fā)送一個消息出去,只要另一端處于連接狀態(tài),那么就可以接收到這個消息。而如果使用的是http、https等的話,這一端你發(fā)送一個消息,另外一段需要刷新一下頁面才能看到消息(當(dāng)然可以搞個定時器)。結(jié)合WebSocket技術(shù),能很快速地實(shí)現(xiàn)一個視頻通話功能。
2. 實(shí)現(xiàn)
導(dǎo)入相關(guān)jar包的依賴,如下:
<?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.7.10</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
上述jar包可能有一些不需要的喔!
2.1 后端實(shí)現(xiàn)
websocket 配置類
GetHttpSessionConfig.class
package com.example.demo.websocket2;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
HttpSession httpSession = (HttpSession) request.getHttpSession();
// 獲取httpsession對象
sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
}
}
ServerEndpointExporter Bean的定義 Config.class
package com.example.demo.websocket2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class Config {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
*websocket服務(wù)器類 WebSocketServer *
package com.example.demo.websocket2;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Component
@ServerEndpoint(value = "/video",configurator = GetHttpSessionConfig.class)
public class WebSocketServer {
//存儲客戶端的連接對象,每個客戶端連接都會產(chǎn)生一個連接對象
private static ConcurrentHashMap<String,WebSocketServer> map = new ConcurrentHashMap();
//每個連接都會有自己的會話
private Session session;
private String account;
@OnOpen
public void open(Session session,EndpointConfig config){
HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
String account = String.valueOf(httpSession.getAttribute("account"));
map.put(account,this);
this.session = session;
this.account = account;
}
@OnClose
public void close(){
map.remove(account);
}
@OnError
public void error(Throwable error){
error.printStackTrace();
}
@OnMessage
public void getMessage(String message) throws IOException {
Set<Map.Entry<String, WebSocketServer>> entries = map.entrySet();
for (Map.Entry<String, WebSocketServer> entry : entries) {
if(!entry.getKey().equals(account)){//將消息轉(zhuǎn)發(fā)到其他非自身客戶端
entry.getValue().send(message);
}
}
}
public void send(String message) throws IOException {
if(session.isOpen()){
session.getBasicRemote().sendText(message);
}
}
public int getConnetNum(){
return map.size();
}
}
2.2 前端頁面實(shí)現(xiàn)
登錄界面的代碼就不在這兒粘貼了,下面主要展示視頻通話界面的代碼(包括css樣式和js代碼都在的)
<html>
<head>
<title>main</title>
<link rel = "stylesheet" href = "https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap-theme.min.css"/>
</head>
<style>
body {
background: #eee;
padding: 5% 0;
}
video {
background: black;
border: 1px solid gray;
}
.call-page {
position: relative;
display: block;
margin: 0 auto;
width: 500px;
height: 500px;
}
#localVideo {
width: 150px;
height: 150px;
position: absolute;
top: 15px;
right: 15px;
}
#remoteVideo {
width: 500px;
height: 500px;
}
</style>
<body>
<div id = "callPage" class = "call-page">
<video id = "localVideo" autoplay></video>
<video id = "remoteVideo" autoplay></video>
<div class = "row text-center">
<div class = "col-md-12">
<input id = "callToUsernameInput" type = "text"
placeholder = "username to call" />
<button id = "callBtn" class = "btn-success btn">Call</button>
<button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button>
</div>
</div>
</div>
<script type="text/javascript">
//our username
var connectedUser;
//connecting to our signaling server
var conn = new WebSocket("ws://localhost:9999/video");
conn.onopen = function () {
console.log("Connected to the signaling server");
};
//when we got a message from a signaling server
conn.onmessage = function (msg) {
console.log("Got message", msg.data);
var data = JSON.parse(msg.data);
switch(data.type) {
case "login":
handleLogin(data.success);
break;
//when somebody wants to call us
case "offer":
handleOffer(data.offer, data.name);
break;
case "answer":
handleAnswer(data.answer);
break;
//when a remote peer sends an ice candidate to us
case "candidate":
handleCandidate(data.candidate);
break;
case "leave":
handleLeave();
break;
default:
break;
}
};
conn.onerror = function (err) {
console.log("Got error", err);
};
//alias for sending JSON encoded messages
function send(message) {
//attach the other peer username to our messages
if (connectedUser) {
message.name = connectedUser;
}
conn.send(JSON.stringify(message));
}
//******
//UI selectors block
//******
var callPage = document.querySelector("#callPage");
var callToUsernameInput = document.querySelector("#callToUsernameInput");
var callBtn = document.querySelector("#callBtn");
var hangUpBtn = document.querySelector("#hangUpBtn");
var localVideo = document.querySelector("#localVideo");
var remoteVideo = document.querySelector("#remoteVideo");
var yourConn;
var stream;
// callPage.style.display = "none";
var PeerConnection = (window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.RTCPeerConnection || undefined);
var RTCSessionDescription = (window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription || undefined);
navigator.getUserMedia = (navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
//**********************
//Starting a peer connection
//**********************
//getting local video stream
navigator.getUserMedia({ video: true, audio: true }, function (myStream) {
stream = myStream;
//displaying local video stream on the page
localVideo.srcObject = stream;
//using Google public stun server
var configuration = {
"iceServers": []
};
yourConn = new PeerConnection(configuration);
// setup stream listening
yourConn.addStream(stream);
//when a remote user adds stream to the peer connection, we display it
yourConn.onaddstream = function (e) {
remoteVideo.srcObject = e.stream;
};
// Setup ice handling
yourConn.onicecandidate = function (event) {
if (event.candidate) {
send({
type: "candidate",
candidate: event.candidate
});
}
};
}, function (error) {
console.log(error);
});
//initiating a call
callBtn.addEventListener("click", function () {
var callToUsername = callToUsernameInput.value;
if (callToUsername.length > 0) {
connectedUser = callToUsername;
// create an offer
yourConn.createOffer(function (offer) {
send({
type: "offer",
offer: offer
});
yourConn.setLocalDescription(offer);
}, function (error) {
alert("Error when creating an offer");
});
}
});
//when somebody sends us an offer
function handleOffer(offer, name) {
connectedUser = name;
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
//create an answer to an offer
yourConn.createAnswer(function (answer) {
yourConn.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
}, function (error) {
alert("Error when creating an answer");
});
}
//when we got an answer from a remote user
function handleAnswer(answer) {
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
}
//when we got an ice candidate from a remote user
function handleCandidate(candidate) {
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
}
//hang up
hangUpBtn.addEventListener("click", function () {
send({
type: "leave"
});
handleLeave();
});
function handleLeave() {
connectedUser = null;
remoteVideo.src = null;
yourConn.close();
yourConn.onicecandidate = null;
yourConn.onaddstream = null;
}
</script>
</body>
</html>
3. 總結(jié)
上述前端代碼參考來自這里:webrtc視頻演示,上述代碼中如果有不懂的讀者可以去仔細(xì)看看這個鏈接里的知識,里面關(guān)于webrtc有詳細(xì)的介紹及實(shí)現(xiàn),不過,沒有講多人的,它只講了一對一的,不過,前一段時間小編在參考一些大佬的實(shí)現(xiàn)思路及自己思考下,也實(shí)現(xiàn)了一個多人的,運(yùn)行結(jié)果如下:文章來源:http://www.zghlxwxcb.cn/news/detail-496836.html
基于SpringBoot,WebSocket,WebRTC實(shí)現(xiàn)多人自習(xí)室功能文章來源地址http://www.zghlxwxcb.cn/news/detail-496836.html
到了這里,關(guān)于一小時教你用SpringBoot+WebSocket+WebRTC實(shí)現(xiàn)視頻通話的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!