0


java版本使用springboot vue websocket webrtc实现视频通话

使用java版本 websocket webrtc实现视频通话

原理简单解释

​ 浏览器提供获取屏幕、音频等媒体数据的接口,

​ 双方的媒体流数据通过Turn服务器传输

websocket传递信令服务

使用技术

  1. java jdk17
  2. springboot 3.2.2
  3. websocket
  4. 前端使用 vue

搭建websocket环境依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

websocket的配置类

packagecom.example.webrtc.config;importcom.example.webrtc.Interceptor.AuthHandshakeInterceptor;importcom.example.webrtc.Interceptor.MyChannelInterceptor;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.messaging.converter.MessageConverter;importorg.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;importorg.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;importorg.springframework.messaging.simp.config.ChannelRegistration;importorg.springframework.messaging.simp.config.MessageBrokerRegistry;importorg.springframework.web.socket.config.annotation.*;importorg.springframework.web.socket.server.standard.ServerEndpointExporter;importjava.util.List;@Configuration@EnableWebSocketMessageBrokerpublicclassWebSocketConfigextendsWebSocketMessageBrokerConfigurationSupportimplementsWebSocketMessageBrokerConfigurer{privatestaticfinalLogger log =LoggerFactory.getLogger(WebSocketConfig.class);@AutowiredprivateAuthHandshakeInterceptor authHandshakeInterceptor;@AutowiredprivateMyChannelInterceptor myChannelInterceptor;@BeanpublicServerEndpointExporterserverEndpointExporter(){returnnewServerEndpointExporter();}@OverridepublicvoidregisterStompEndpoints(StompEndpointRegistry registry){
        registry.addEndpoint("/chat-websocket").setAllowedOriginPatterns("*").addInterceptors(authHandshakeInterceptor).setAllowedOriginPatterns("*")//   .setHandshakeHandler(myHandshakeHandler).withSockJS();}@OverridepublicvoidconfigureWebSocketTransport(WebSocketTransportRegistration registry){
            registry.setMessageSizeLimit(Integer.MAX_VALUE);
            registry.setSendBufferSizeLimit(Integer.MAX_VALUE);super.configureWebSocketTransport(registry);}@OverridepublicvoidconfigureMessageBroker(MessageBrokerRegistry registry){//客户端需要把消息发送到/message/xxx地址
        registry.setApplicationDestinationPrefixes("/webSocket");//服务端广播消息的路径前缀,客户端需要相应订阅/topic/yyy这个地址的消息
        registry.enableSimpleBroker("/topic","/user");//给指定用户发送消息的路径前缀,默认值是/user/
        registry.setUserDestinationPrefix("/user/");}@OverridepublicvoidconfigureClientInboundChannel(ChannelRegistration registration){
        registration.interceptors(myChannelInterceptor);}@OverridepublicvoidconfigureClientOutboundChannel(ChannelRegistration registration){WebSocketMessageBrokerConfigurer.super.configureClientOutboundChannel(registration);}@OverridepublicvoidaddArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers){WebSocketMessageBrokerConfigurer.super.addArgumentResolvers(argumentResolvers);}@OverridepublicvoidaddReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers){WebSocketMessageBrokerConfigurer.super.addReturnValueHandlers(returnValueHandlers);}@OverridepublicbooleanconfigureMessageConverters(List<MessageConverter> messageConverters){returnWebSocketMessageBrokerConfigurer.super.configureMessageConverters(messageConverters);}}

控制层 WebSocketController

packagecom.example.webrtc.controller;importcom.example.webrtc.config.Message;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.messaging.handler.annotation.MessageMapping;importorg.springframework.messaging.simp.SimpMessagingTemplate;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.security.Principal;importjava.util.HashMap;importjava.util.Map;importjava.util.concurrent.atomic.AtomicInteger;// 私信聊天的控制器@RestControllerpublicclassWebSocketController{@AutowiredprivateSimpMessagingTemplate messagingTemplate;privateAtomicInteger i=newAtomicInteger(1);@RequestMapping("/user")publicStringfindUser(){return"00"+i.decrementAndGet();}@MessageMapping("/api/chat")//在springmvc 中可以直接获得principal,principal 中包含当前用户的信息publicvoidhandleChat(Principal principal,Message messagePara){String currentUserName = principal.getName();System.out.println(currentUserName);try{
            messagePara.setFrom(principal.getName());System.out.println("from"+ messagePara.getFrom());
            messagingTemplate.convertAndSendToUser(messagePara.getTo(),"/queue/notifications",
                    messagePara);}catch(Exception e){// 打印异常
            e.printStackTrace();}}}

前端交互拨号index.vue

<template><div class="play-audio"><h2 style="text-align: center;">播放页面</h2><div class="main-box"><video ref="localVideo"class="video" autoplay="autoplay"></video><video ref="remoteVideo"class="video" height="500px" autoplay="autoplay"></video></div><div style="text-align: center;"><el-button @click="requestConnect()" ref="callBtn">开始对讲</el-button><el-button @click="hangupHandle()" ref="hangupBtn">结束对讲</el-button></div><div style="text-align: center;"><label for="name">发送人:</label><input type="text" id="name" readonly v-model="userId"class="form-control"/></div><div style="text-align: center;"><label for="name">接收人:</label><input type="text" id="name" v-model="toUserId"class="form-control"/></div></div></template><el-dialog :title="'提示'":visible.sync="dialogVisible" width="30%"><span>{{ toUserId +'请求连接!'}}</span><span slot="footer"class="dialog-footer"><el-button @click="handleClose">取 消</el-button><el-button type="primary"@click="dialogVisibleYes">确 定</el-button></span></el-dialog><script>importrequest from '@/utils/reeques'
importWebsocket from '@/utils/websocket'
importStomp from "stompjs";importSockJS from "sockjs-client";importadapter from "webrtc-adapter";importaxios from 'axios'

export default{data(){return{
      stompClient:null,
      userId:'001',
      socket:null,
      toUserId: '',
      localStream:null,
      remoteStream:null,
      localVideo:null,
      remoteVideo:null,
      callBtn:null,
      hangupBtn:null,
      peerConnection:null,
      dialogVisible:false,
      msg: '',
      config:{
        iceServers:[{urls: 'stun:global.stun.twilio.com:3478?transport=udp'}],}};},
  computed:{},
  methods:{handleClose(){this.dialogVisible =false},dialogVisibleYes(){var _self =this;this.dialogVisible =false
      _self.startHandle().then(()=>{
        _self.stompClient.send("/api/chat", _self.toUserId,{'type':'start'})})},requestConnect(){
      let that =this;if(!that.toUserId){alert('请输入对方id')returnfalse}elseif(!that.stompClient){alert('请先打开websocket')returnfalse}elseif(that.toUserId == that.userId){alert('自己不能和自己连接')returnfalse}//准备连接
      that.startHandle().then(()=>{
        that.stompClient.send("/api/chat", that.toUserId,{'type': 'connect'})})},startWebsocket(user){
      let that =this;
      that.stompClient =newWebsocket(user);
      that.stompClient.connect(()=>{
        that.stompClient.subscribe("/user/"+ that.userId +"/queue/notifications", function (result){
          that.onmessage(result)})})},gotLocalMediaStream(mediaStream){var _self =this;
      _self.localVideo.srcObject = mediaStream;
      _self.localStream = mediaStream;// _self.callBtn.disabled = false;},createConnection(){var _self =this;
      _self.peerConnection =newRTCPeerConnection()if(_self.localStream){// 视频轨道const videoTracks = _self.localStream.getVideoTracks();// 音频轨道const audioTracks = _self.localStream.getAudioTracks();// 判断视频轨道是否有值if(videoTracks.length >0){
          console.log(`使用的设备为: ${videoTracks[0].label}.`);}// 判断音频轨道是否有值if(audioTracks.length >0){
          console.log(`使用的设备为: ${audioTracks[0].label}.`);}

        _self.localStream.getTracks().forEach((track)=>{
          _self.peerConnection.addTrack(track, _self.localStream)})}// 监听返回的 Candidate
      _self.peerConnection.addEventListener('icecandidate', _self.handleConnection);// 监听 ICE 状态变化
      _self.peerConnection.addEventListener('iceconnectionstatechange', _self.handleConnectionChange)//拿到流的时候调用
      _self.peerConnection.addEventListener('track', _self.gotRemoteMediaStream);},startConnection(){var _self =this;// _self.callBtn.disabled  = true;// _self.hangupBtn.disabled = false;// 发送offer
      _self.peerConnection.createOffer().then(description =>{
        console.log(`本地创建offer返回的sdp:\n${description.sdp}`)// 将 offer 保存到本地
        _self.peerConnection.setLocalDescription(description).then(()=>{
          console.log('local 设置本地描述信息成功');// 本地设置描述并将它发送给远端// _self.socket.send(JSON.stringify({//   'userId': _self.userId,//   'toUserId': _self.toUserId,//   'message': description// }));
          _self.stompClient.send("/api/chat", _self.toUserId, description)}).catch((err)=>{
          console.log('local 设置本地描述信息错误', err)});}).catch((err)=>{
          console.log('createdOffer 错误', err);});},
    async startHandle(){this.callBtn =this.$refs.callBtn
      this.hangupBtn =this.$refs.hangupBtn
      this.remoteVideo =this.$refs.remoteVideo
      this.localVideo =this.$refs.localVideo
      var _self =this;// 1.获取本地音视频流// 调用 getUserMedia API 获取音视频流
      let constraints ={
        video:true,
        audio:{// 设置回音消除
          noiseSuppression:true,// 设置降噪
          echoCancellation:true,}}
      navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
      await navigator.mediaDevices.getUserMedia(constraints).then(_self.gotLocalMediaStream).catch((err)=>{
          console.log('getUserMedia 错误', err);//创建点对点连接对象});

      _self.createConnection();},onmessage(e){var _self =this;const description = e.message
      _self.toUserId = e.from
      switch(description.type){case 'connect':
          _self.dialogVisible =truethis.$confirm(_self.toUserId +'请求连接!','提示',{}).then(()=>{
            _self.startHandle().then(()=>{
              _self.stompClient.send("/api/chat", _self.toUserId,{'type':'start'})})}).catch(()=>{});break;case'start'://同意连接之后开始连接
          _self.startConnection()break;case'offer':
          _self.peerConnection.setRemoteDescription(newRTCSessionDescription(description)).then(()=>{}).catch((err)=>{
            console.log('local 设置远端描述信息错误', err);});

          _self.peerConnection.createAnswer().then(function (answer){

            _self.peerConnection.setLocalDescription(answer).then(()=>{
              console.log('设置本地answer成功!');}).catch((err)=>{
              console.error('设置本地answer失败', err);});
            _self.stompClient.send("/api/chat", _self.toUserId, answer)}).catch(e =>{
            console.error(e)});break;case 'icecandidate':// 创建 RTCIceCandidate 对象
          let newIceCandidate =newRTCIceCandidate(description.icecandidate);// 将本地获得的 Candidate 添加到远端的 RTCPeerConnection 对象中
          _self.peerConnection.addIceCandidate(newIceCandidate).then(()=>{
            console.log(`addIceCandidate 成功`);}).catch((error)=>{
            console.log(`addIceCandidate 错误:\n` + `${error.toString()}.`);});break;case'answer':
          _self.peerConnection.setRemoteDescription(newRTCSessionDescription(description)).then(()=>{
            console.log('设置remote answer成功!');}).catch((err)=>{
            console.log('设置remote answer错误', err);});break;default:break;}},hangupHandle(){var _self =this;// 关闭连接并设置为空
      _self.peerConnection.close();
      _self.peerConnection =null;// _self.hangupBtn.disabled = true;// _self.callBtn.disabled = false;

      _self.localStream.getTracks().forEach((track)=>{
        track.stop()})},handleConnection(event){var _self =this;// 获取到触发 icecandidate 事件的 RTCPeerConnection 对象// 获取到具体的Candidate
      console.log("handleConnection")const peerConnection = event.target;const icecandidate = event.candidate;if(icecandidate){
        _self.stompClient.send("/api/chat", _self.toUserId,{
          type: 'icecandidate',
          icecandidate: icecandidate
        })}},gotRemoteMediaStream(event){var _self =this;
      console.log('remote 开始接受远端流')if(event.streams[0]){
        console.log(' remoteVideo')
        _self.remoteVideo.srcObject = event.streams[0];
        _self.remoteStream = event.streams[0];}},handleConnectionChange(event){const peerConnection = event.target;
      console.log('ICE state change event: ', event);
      console.log(`ICE state: ` + `${peerConnection.iceConnectionState}.`);},log(v){
      console.log(v)},},created(){
    let that =this;request({
      url:'/user',
      method:'get',
      params:{}}).then(response =>{
      console.log(response.data)
      that.userId = response.data;this.startWebsocket(response.data)
      debugger
    })
    debugger

  }}</script><style lang="scss">.spreadsheet {
  padding:010px;
  margin:20px 0;}.main-box {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;}</style>

最终演示效果

在这里插入图片描述

具体代码查看


本文转载自: https://blog.csdn.net/weixin_44327322/article/details/135701642
版权归原作者 孤独和弦 所有, 如有侵权,请联系我们删除。

“java版本使用springboot vue websocket webrtc实现视频通话”的评论:

还没有评论