spring boot 实现 Kurento 一对一浏览器视频聊天

一、环境配置

1. 安装了Kurento Media Server(KMS) Ubuntu16.04(Xenial)。

           首先解决服务系统,若有腾讯云或阿里云的Ubuntu16服务器则直接安装KMS服务;没有的可通过VMware虚拟机在本地机器上安装Ubuntu16.04(Xenial)之后再安装KMS服务;

        a. 先说没有云服务器的情况,没有虚拟机的可通过本地址下载https://www.vmware.com/products/workstation-pro/workstation-pro-evaluation.html?productId=686&rPId=25455

       没有Ubuntu的可通过http://releases.ubuntu.com/16.04下载注意选择无窗口的版本ubuntu-16.04.5-server-amd64.iso

     遇到的情况:在虚拟机上安装系统时可能会遇到“Intel VT-x 处于禁用状态”,百度经验https://jingyan.baidu.com/article/fc07f98976710e12ffe519de.html;装好系统后先切换到管理员权限下,先设置root密码命令:

sudo passwd root

输入root的密码和确认密码,之后在执行命令:

su root

输入刚才设置新密码;新装的Ubuntu是没有vim编辑器的执行命令自动安装:

apt-get install vim

 

       b.服务器有了之后便可安装KMS服务了依次执行以下命令便可

REPO="xenial"

echo "deb http://ubuntu.kurento.org $REPO kms6" | sudo tee /etc/apt/sources.list.d/kurento.list

wget http://ubuntu.kurento.org/kurento.gpg.key -O - | sudo apt-key add -

apt update

useradd -U -m kurento

apt install -y kurento-media-server-6.0

systemctl start kurento-media-server-6.0

  安装成功后KMS默认运行在8888端口通过命令: netstat -tunlp|grep 8888 查看;

配置STUN和TURN服务器,执行命令:

vim    /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini

编辑

stunServerAddress = <serverIp>
stunServerPort = <serverPort>

为:(注意每行前面没有分号)

stunServerAddress = 77.72.174.163

stunServerPort = 3478

添加 H.264 支持 执行命令:

apt install -y openh264-gst-plugins-bad-1.5

重启 kurento server

systemctl restart kurento-media-server-6.0

到 此KMS服务安装完毕。

2. 基于spring boot 的KurentoClient。

a.先来一个spring boot项目,打开链接 https://start.spring.io/ ,设置选项如图,点击绿色按钮下载一个空的spring boot项目然后导入编辑器,

配置application.properties

# LOGGING
logging.level.root=INFO
logging.level.org.apache=WARN
logging.level.org.springframework=WARN
logging.level.org.kurento=INFO
logging.level.org.kurento.tutorial=INFO

# OUTPUT
# Terminal color output; one of [ALWAYS, DETECT, NEVER]
spring.output.ansi.enabled=DETECT


# ----------------------------------------
# WEB PROPERTIES
# ----------------------------------------

# EMBEDDED SERVER CONFIGURATION
server.port=8443

 

配置pom.xml

        <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

        <dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		
		<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-starter-websocket</artifactId>
		</dependency>
		
		<dependency> 
	      <groupId>org.kurento</groupId> 
	      <artifactId>kurento-client</artifactId> 
	      <version>6.7.1</version>
	    </dependency> 
	   <dependency> 
	      <groupId>org.kurento</groupId> 
	      <artifactId>kurento-utils-js</artifactId> 
	      <version>6.7.0</version>
	   </dependency> 
	   <dependency> 
	      <groupId>org.webjars</groupId> 
	      <artifactId>webjars-locator</artifactId> 
	      <version>0.34</version>
	   </dependency> 
	   <dependency> 
	      <groupId>org.webjars.bower</groupId > 
	      <artifactId>bootstrap</artifactId> 
	      <version>4.1.2</version>
	   </dependency> 
	   
	   <dependency> 
	      <groupId>org.webjars.bower</groupId> 
	      <artifactId>adapter.js</artifactId > 
	      <version>4.0.1</version>
	   </dependency> 
	   <dependency> 
	      <groupId>org.webjars.bower</groupId> 
	      <artifactId>jquery</artifactId> 
	      <version>3.3.1</version>
	   </dependency> 
	   <dependency> 
	      <groupId>org.webjars.bower</groupId> 
	      <artifactId>ekko-lightbox</artifactId> 
	      <version>5.2.0</version>
	   </dependency>

	</dependencies>

CallMediaPipeline.java

import java.text.SimpleDateFormat;
import java.util.Date;

import org.kurento.client.KurentoClient;
import org.kurento.client.MediaPipeline;
import org.kurento.client.RecorderEndpoint;
import org.kurento.client.WebRtcEndpoint;


public class CallMediaPipeline {

  private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-S");
  public static final String RECORDING_PATH = "file:///tmp/" + df.format(new Date()) + "-";
  public static final String RECORDING_EXT = ".webm";

  private final MediaPipeline pipeline;
  private final WebRtcEndpoint webRtcCaller;
  private final WebRtcEndpoint webRtcCallee;
  private final RecorderEndpoint recorderCaller;
  private final RecorderEndpoint recorderCallee;

  public CallMediaPipeline(KurentoClient kurento, String from, String to) {

    // Media pipeline
    pipeline = kurento.createMediaPipeline();

    // Media Elements (WebRtcEndpoint, RecorderEndpoint)
    webRtcCaller = new WebRtcEndpoint.Builder(pipeline).build();
    webRtcCallee = new WebRtcEndpoint.Builder(pipeline).build();

    recorderCaller = new RecorderEndpoint.Builder(pipeline, RECORDING_PATH + from + RECORDING_EXT)
        .build();
    recorderCallee = new RecorderEndpoint.Builder(pipeline, RECORDING_PATH + to + RECORDING_EXT)
        .build();

    // Connections
    webRtcCaller.connect(webRtcCallee);
    webRtcCaller.connect(recorderCaller);

    webRtcCallee.connect(webRtcCaller);
    webRtcCallee.connect(recorderCallee);
  }

  public void record() {
    recorderCaller.record();
    recorderCallee.record();
  }

  public String generateSdpAnswerForCaller(String sdpOffer) {
    return webRtcCaller.processOffer(sdpOffer);
  }

  public String generateSdpAnswerForCallee(String sdpOffer) {
    return webRtcCallee.processOffer(sdpOffer);
  }

  public MediaPipeline getPipeline() {
    return pipeline;
  }

  public WebRtcEndpoint getCallerWebRtcEp() {
    return webRtcCaller;
  }

  public WebRtcEndpoint getCalleeWebRtcEp() {
    return webRtcCallee;
  }
}


One2OneCallRecApp.java

package org.kurento.tutorial.one2onecallrec;

import org.kurento.client.KurentoClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;


@SpringBootApplication
@EnableWebSocket
public class One2OneCallRecApp implements WebSocketConfigurer {

  @Bean
  public CallHandler callHandler() {
    return new CallHandler();
  }

  @Bean
  public UserRegistry registry() {
    return new UserRegistry();
  }

  @Bean
  public KurentoClient kurentoClient() {
    //此处IP地址为安装了KMS服务的Ubuntu 16 IP地址,若服务器为https的则'ws'改为‘wss’
    //若KMS安装在VMware上则为该虚拟机在局域网的IP
    return KurentoClient.create("ws://192.168.0.128:8888/kurento");
  }

  @Override
  public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(callHandler(), "/call").setAllowedOrigins("*");
  }

  public static void main(String[] args) throws Exception {
    SpringApplication.run(One2OneCallRecApp.class, args);
  }

}


OneToOneCallHandler.java

package org.kurento.tutorial.one2onecallrec;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

import org.kurento.client.EndOfStreamEvent;
import org.kurento.client.EventListener;
import org.kurento.client.IceCandidate;
import org.kurento.client.IceCandidateFoundEvent;
import org.kurento.client.KurentoClient;
import org.kurento.client.MediaPipeline;
import org.kurento.jsonrpc.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;


public class CallHandler extends TextWebSocketHandler {

  private static final Logger log = LoggerFactory.getLogger(CallHandler.class);
  private static final Gson gson = new GsonBuilder().create();

  private final ConcurrentHashMap<String, MediaPipeline> pipelines = new ConcurrentHashMap<>();

  @Autowired
  private KurentoClient kurento;

  @Autowired
  private UserRegistry registry;

  @Override
  public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    JsonObject jsonMessage = gson.fromJson(message.getPayload(), JsonObject.class);
    UserSession user = registry.getBySession(session);

    if (user != null) {
      log.debug("Incoming message from user '{}': {}", user.getName(), jsonMessage);
    } else {
      log.debug("Incoming message from new user: {}", jsonMessage);
    }

    switch (jsonMessage.get("id").getAsString()) {
      case "register":
        register(session, jsonMessage);
        break;
      case "call":
        call(user, jsonMessage);
        break;
      case "incomingCallResponse":
        incomingCallResponse(user, jsonMessage);
        break;
      case "play":
        play(user, jsonMessage);
        break;
      case "onIceCandidate": {
        JsonObject candidate = jsonMessage.get("candidate").getAsJsonObject();

        if (user != null) {
          IceCandidate cand =
              new IceCandidate(candidate.get("candidate").getAsString(), candidate.get("sdpMid")
                  .getAsString(), candidate.get("sdpMLineIndex").getAsInt());
          user.addCandidate(cand);
        }
        break;
      }
      case "stop":
        stop(session);
        releasePipeline(user);
        break;
      case "stopPlay":
        releasePipeline(user);
        break;
      default:
        break;
    }
  }

  private void register(WebSocketSession session, JsonObject jsonMessage) throws IOException {
    String name = jsonMessage.getAsJsonPrimitive("name").getAsString();

    UserSession caller = new UserSession(session, name);
    String responseMsg = "accepted";
    if (name.isEmpty()) {
      responseMsg = "rejected: empty user name";
    } else if (registry.exists(name)) {
      responseMsg = "rejected: user '" + name + "' already registered";
    } else {
      registry.register(caller);
    }

    JsonObject response = new JsonObject();
    response.addProperty("id", "registerResponse");
    response.addProperty("response", responseMsg);
    caller.sendMessage(response);
  }

  private void call(UserSession caller, JsonObject jsonMessage) throws IOException {
    String to = jsonMessage.get("to").getAsString();
    String from = jsonMessage.get("from").getAsString();
    JsonObject response = new JsonObject();

    if (registry.exists(to)) {
      caller.setSdpOffer(jsonMessage.getAsJsonPrimitive("sdpOffer").getAsString());
      caller.setCallingTo(to);

      response.addProperty("id", "incomingCall");
      response.addProperty("from", from);

      UserSession callee = registry.getByName(to);
      callee.sendMessage(response);
      callee.setCallingFrom(from);
    } else {
      response.addProperty("id", "callResponse");
      response.addProperty("response", "rejected");
      response.addProperty("message", "user '" + to + "' is not registered");

      caller.sendMessage(response);
    }
  }

  private void incomingCallResponse(final UserSession callee, JsonObject jsonMessage)
      throws IOException {
    String callResponse = jsonMessage.get("callResponse").getAsString();
    String from = jsonMessage.get("from").getAsString();
    final UserSession calleer = registry.getByName(from);
    String to = calleer.getCallingTo();

    if ("accept".equals(callResponse)) {
      log.debug("Accepted call from '{}' to '{}'", from, to);

      CallMediaPipeline callMediaPipeline = new CallMediaPipeline(kurento, from, to);
      pipelines.put(calleer.getSessionId(), callMediaPipeline.getPipeline());
      pipelines.put(callee.getSessionId(), callMediaPipeline.getPipeline());

      callee.setWebRtcEndpoint(callMediaPipeline.getCalleeWebRtcEp());
      callMediaPipeline.getCalleeWebRtcEp().addIceCandidateFoundListener(
          new EventListener<IceCandidateFoundEvent>() {

            @Override
            public void onEvent(IceCandidateFoundEvent event) {
              JsonObject response = new JsonObject();
              response.addProperty("id", "iceCandidate");
              response.add("candidate", JsonUtils.toJsonObject(event.getCandidate()));
              try {
                synchronized (callee.getSession()) {
                  callee.getSession().sendMessage(new TextMessage(response.toString()));
                }
              } catch (IOException e) {
                log.debug(e.getMessage());
              }
            }
          });

      String calleeSdpOffer = jsonMessage.get("sdpOffer").getAsString();
      String calleeSdpAnswer = callMediaPipeline.generateSdpAnswerForCallee(calleeSdpOffer);
      JsonObject startCommunication = new JsonObject();
      startCommunication.addProperty("id", "startCommunication");
      startCommunication.addProperty("sdpAnswer", calleeSdpAnswer);

      synchronized (callee) {
        callee.sendMessage(startCommunication);
      }

      callMediaPipeline.getCalleeWebRtcEp().gatherCandidates();

      String callerSdpOffer = registry.getByName(from).getSdpOffer();

      calleer.setWebRtcEndpoint(callMediaPipeline.getCallerWebRtcEp());
      callMediaPipeline.getCallerWebRtcEp().addIceCandidateFoundListener(
          new EventListener<IceCandidateFoundEvent>() {

            @Override
            public void onEvent(IceCandidateFoundEvent event) {
              JsonObject response = new JsonObject();
              response.addProperty("id", "iceCandidate");
              response.add("candidate", JsonUtils.toJsonObject(event.getCandidate()));
              try {
                synchronized (calleer.getSession()) {
                  calleer.getSession().sendMessage(new TextMessage(response.toString()));
                }
              } catch (IOException e) {
                log.debug(e.getMessage());
              }
            }
          });

      String callerSdpAnswer = callMediaPipeline.generateSdpAnswerForCaller(callerSdpOffer);

      JsonObject response = new JsonObject();
      response.addProperty("id", "callResponse");
      response.addProperty("response", "accepted");
      response.addProperty("sdpAnswer", callerSdpAnswer);

      synchronized (calleer) {
        calleer.sendMessage(response);
      }

      callMediaPipeline.getCallerWebRtcEp().gatherCandidates();

      callMediaPipeline.record();

    } else {
      JsonObject response = new JsonObject();
      response.addProperty("id", "callResponse");
      response.addProperty("response", "rejected");
      calleer.sendMessage(response);
    }
  }

  public void stop(WebSocketSession session) throws IOException {
    // Both users can stop the communication. A 'stopCommunication'
    // message will be sent to the other peer.
    UserSession stopperUser = registry.getBySession(session);
    if (stopperUser != null) {
      UserSession stoppedUser =
          (stopperUser.getCallingFrom() != null) ? registry.getByName(stopperUser.getCallingFrom())
              : stopperUser.getCallingTo() != null ? registry.getByName(stopperUser.getCallingTo())
                  : null;

              if (stoppedUser != null) {
                JsonObject message = new JsonObject();
                message.addProperty("id", "stopCommunication");
                stoppedUser.sendMessage(message);
                stoppedUser.clear();
              }
              stopperUser.clear();
    }
  }

  public void releasePipeline(UserSession session) {
    String sessionId = session.getSessionId();

    if (pipelines.containsKey(sessionId)) {
      pipelines.get(sessionId).release();
      pipelines.remove(sessionId);
    }
    session.setWebRtcEndpoint(null);
    session.setPlayingWebRtcEndpoint(null);

    // set to null the endpoint of the other user
    UserSession stoppedUser =
        (session.getCallingFrom() != null) ? registry.getByName(session.getCallingFrom())
            : registry.getByName(session.getCallingTo());
        stoppedUser.setWebRtcEndpoint(null);
        stoppedUser.setPlayingWebRtcEndpoint(null);
  }

  private void play(final UserSession session, JsonObject jsonMessage) throws IOException {
    String user = jsonMessage.get("user").getAsString();
    log.debug("Playing recorded call of user '{}'", user);

    JsonObject response = new JsonObject();
    response.addProperty("id", "playResponse");

    if (registry.getByName(user) != null && registry.getBySession(session.getSession()) != null) {
      final PlayMediaPipeline playMediaPipeline =
          new PlayMediaPipeline(kurento, user, session.getSession());

      session.setPlayingWebRtcEndpoint(playMediaPipeline.getWebRtc());

      playMediaPipeline.getPlayer().addEndOfStreamListener(new EventListener<EndOfStreamEvent>() {
        @Override
        public void onEvent(EndOfStreamEvent event) {
          UserSession user = registry.getBySession(session.getSession());
          releasePipeline(user);
          playMediaPipeline.sendPlayEnd(session.getSession());
        }
      });

      playMediaPipeline.getWebRtc().addIceCandidateFoundListener(
          new EventListener<IceCandidateFoundEvent>() {

            @Override
            public void onEvent(IceCandidateFoundEvent event) {
              JsonObject response = new JsonObject();
              response.addProperty("id", "iceCandidate");
              response.add("candidate", JsonUtils.toJsonObject(event.getCandidate()));
              try {
                synchronized (session) {
                  session.getSession().sendMessage(new TextMessage(response.toString()));
            }
              } catch (IOException e) {
                log.debug(e.getMessage());
              }
            }
          });

      String sdpOffer = jsonMessage.get("sdpOffer").getAsString();
      String sdpAnswer = playMediaPipeline.generateSdpAnswer(sdpOffer);

      response.addProperty("response", "accepted");

      response.addProperty("sdpAnswer", sdpAnswer);

      playMediaPipeline.play();
      pipelines.put(session.getSessionId(), playMediaPipeline.getPipeline());
      synchronized (session.getSession()) {
        session.sendMessage(response);
      }

      playMediaPipeline.getWebRtc().gatherCandidates();

    } else {
      response.addProperty("response", "rejected");
      response.addProperty("error", "No recording for user '" + user
          + "'. Please type a correct user in the 'Peer' field.");
      session.getSession().sendMessage(new TextMessage(response.toString()));
    }
  }

  @Override
  public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    stop(session);
    registry.removeBySession(session);
  }

}


OneToOneUserRegistry.java

package org.kurento.tutorial.one2onecallrec;

import java.util.concurrent.ConcurrentHashMap;

import org.springframework.web.socket.WebSocketSession;

public class UserRegistry {

  private ConcurrentHashMap<String, UserSession> usersByName = new ConcurrentHashMap<>();
  private ConcurrentHashMap<String, UserSession> usersBySessionId = new ConcurrentHashMap<>();

  public void register(UserSession user) {
    usersByName.put(user.getName(), user);
    usersBySessionId.put(user.getSession().getId(), user);
  }

  public UserSession getByName(String name) {
    return usersByName.get(name);
  }

  public UserSession getBySession(WebSocketSession session) {
    return usersBySessionId.get(session.getId());
  }

  public boolean exists(String name) {
    return usersByName.keySet().contains(name);
  }

  public UserSession removeBySession(WebSocketSession session) {
    final UserSession user = getBySession(session);
    if (user != null) {
      usersByName.remove(user.getName());
      usersBySessionId.remove(session.getId());
    }
    return user;
  }

}


OneToOneUserSession.java

package org.kurento.tutorial.one2onecallrec;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.kurento.client.IceCandidate;
import org.kurento.client.WebRtcEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import com.google.gson.JsonObject;

public class UserSession {

  private static final Logger log = LoggerFactory.getLogger(UserSession.class);

  private final String name;
  private final WebSocketSession session;

  private String sdpOffer;
  private String callingTo;
  private String callingFrom;
  private WebRtcEndpoint webRtcEndpoint;
  private WebRtcEndpoint playingWebRtcEndpoint;
  private final List<IceCandidate> candidateList = new ArrayList<>();

  public UserSession(WebSocketSession session, String name) {
    this.session = session;
    this.name = name;
  }

  public WebSocketSession getSession() {
    return session;
  }

  public String getName() {
    return name;
  }

  public String getSdpOffer() {
    return sdpOffer;
  }

  public void setSdpOffer(String sdpOffer) {
    this.sdpOffer = sdpOffer;
  }

  public String getCallingTo() {
    return callingTo;
  }

  public void setCallingTo(String callingTo) {
    this.callingTo = callingTo;
  }

  public String getCallingFrom() {
    return callingFrom;
  }

  public void setCallingFrom(String callingFrom) {
    this.callingFrom = callingFrom;
  }

  public void sendMessage(JsonObject message) throws IOException {
    log.debug("Sending message from user '{}': {}", name, message);
    session.sendMessage(new TextMessage(message.toString()));
  }

  public String getSessionId() {
    return session.getId();
  }

  public void setWebRtcEndpoint(WebRtcEndpoint webRtcEndpoint) {
    this.webRtcEndpoint = webRtcEndpoint;

    if (this.webRtcEndpoint != null) {
      for (IceCandidate e : candidateList) {
        this.webRtcEndpoint.addIceCandidate(e);
      }
      this.candidateList.clear();
    }
  }

  public void addCandidate(IceCandidate candidate) {
    if (this.webRtcEndpoint != null) {
      this.webRtcEndpoint.addIceCandidate(candidate);
    } else {
      candidateList.add(candidate);
    }

    if (this.playingWebRtcEndpoint != null) {
      this.playingWebRtcEndpoint.addIceCandidate(candidate);
    }
  }

  public WebRtcEndpoint getPlayingWebRtcEndpoint() {
    return playingWebRtcEndpoint;
  }

  public void setPlayingWebRtcEndpoint(WebRtcEndpoint playingWebRtcEndpoint) {
    this.playingWebRtcEndpoint = playingWebRtcEndpoint;
  }

  public void clear() {
    this.webRtcEndpoint = null;
    this.candidateList.clear();
  }
}


PlayMediaPipeline.java 

package org.kurento.tutorial.one2onecallrec;

import static org.kurento.tutorial.one2onecallrec.CallMediaPipeline.RECORDING_EXT;
import static org.kurento.tutorial.one2onecallrec.CallMediaPipeline.RECORDING_PATH;

import java.io.IOException;

import org.kurento.client.ErrorEvent;
import org.kurento.client.EventListener;
import org.kurento.client.KurentoClient;
import org.kurento.client.MediaPipeline;
import org.kurento.client.PlayerEndpoint;
import org.kurento.client.WebRtcEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import com.google.gson.JsonObject;

public class PlayMediaPipeline {

  private static final Logger log = LoggerFactory.getLogger(PlayMediaPipeline.class);

  private final MediaPipeline pipeline;
  private WebRtcEndpoint webRtc;
  private final PlayerEndpoint player;

  public PlayMediaPipeline(KurentoClient kurento, String user, final WebSocketSession session) {
    // Media pipeline
    pipeline = kurento.createMediaPipeline();

    // Media Elements (WebRtcEndpoint, PlayerEndpoint)
    webRtc = new WebRtcEndpoint.Builder(pipeline).build();
    player = new PlayerEndpoint.Builder(pipeline, RECORDING_PATH + user + RECORDING_EXT).build();

    // Connection
    player.connect(webRtc);

    // Player listeners
    player.addErrorListener(new EventListener<ErrorEvent>() {
      @Override
      public void onEvent(ErrorEvent event) {
        log.info("ErrorEvent: {}", event.getDescription());
        sendPlayEnd(session);
      }
    });
  }

  public void sendPlayEnd(WebSocketSession session) {
    try {
      JsonObject response = new JsonObject();
      response.addProperty("id", "playEnd");
      session.sendMessage(new TextMessage(response.toString()));
    } catch (IOException e) {
      log.error("Error sending playEndOfStream message", e);
    }

    // Release pipeline
    pipeline.release();
    this.webRtc = null;
  }

  public void play() {
    player.play();
  }

  public String generateSdpAnswer(String sdpOffer) {
    return webRtc.processOffer(sdpOffer);
  }

  public MediaPipeline getPipeline() {
    return pipeline;
  }

  public WebRtcEndpoint getWebRtc() {
    return webRtc;
  }

  public PlayerEndpoint getPlayer() {
    return player;
  }

}

添加完六个Java文档后若启动不报错则说名成功连接上了KMS服务 。

b.后台的工作做完了,来前端的

编辑onetoone.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="expires" content="0">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="./img/kurento.png" type="image/png" />

<link rel="stylesheet"
	href="webjars/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet"
	href="webjars/ekko-lightbox/dist/ekko-lightbox.min.css">
<link rel="stylesheet" href="webjars/demo-console/index.css">
<link rel="stylesheet" href="css/kurento.css">

<script src="webjars/jquery/dist/jquery.min.js"></script>
<script src="webjars/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="webjars/ekko-lightbox/dist/ekko-lightbox.min.js"></script>
<script src="https://draggabilly.desandro.com/draggabilly.pkgd.min.js"></script>
<script src="./js/adapter.js"></script>
<script src="./js/console.js"></script>


<script src="./js/kurento-utils.js"></script>
<script src="./js/one2one.js"></script>
<title>Kurento Tutorial: Video Call 1 to 1 with WebRTC with
	recording</title>
</head>
<body>
	<header>
		<div class="navbar navbar-inverse navbar-fixed-top">
			<div class="container">
				<div class="navbar-header">
					<button type="button" class="navbar-toggle" data-toggle="collapse"
						data-target=".navbar-collapse"></button>
					<a class="navbar-brand" href="./">Kurento Tutorial</a>
				</div>
				<div class="collapse navbar-collapse"
					id="bs-example-navbar-collapse-1">
					<ul class="nav navbar-nav navbar-right">
						<li><a
							href="https://github.com/Kurento/kurento-tutorial-java/tree/master/kurento-one2one-call-recording"><span
								class="glyphicon glyphicon-file"></span> Source Code</a></li>
					</ul>
				</div>
			</div>
		</div>
	</header>
	<div class="container">
		<div class="page-header">
			<h1>Tutorial: Video Call 1 to 1 with recording</h1>
			<p>
				This web application consists on an one to one video call using <a
					href="http://www.webrtc.org/">WebRTC</a>. It uses the Kurento
				capabilities or recording of the video communication. This
				application implements two different Media Pipelines. The <a
					href="./img/pipeline1.png" data-toggle="lightbox"
					data-title="Video Call 1 to 1 with recording, First Media Pipeline"
					data-footer="Two interconnected WebRtcEnpoints Media Elements with recording (RecorderEndpoint)">first
					Media Pipeline</a> is used to communicate two peers and it is composed
				by two interconnected <i>WebRtcEndpoints</i> with two <i>RecorderEndpoints</i>
				to carry out the recording. The recorded stream will be stored in
				the file system of the Kurento Media Server. Then, a <a
					href="./img/pipeline2.png" data-toggle="lightbox"
					data-title=" Video Call 1 to 1 with recordging, Second Media Pipeline"
					data-footer="A PlayerEndpoint (reading the recorded file in the Kurento Media Server) connected to a WebRtcEnpoint in receive-only mode">second
					Media Pipeline</a> is used to play the recorded media. To run this demo
				follow these steps:
			</p>
			<ol>
				<li>Open this page with a browser compliant with WebRTC
					(Chrome, Firefox).</li>
				<li>Type a nick in the field <i>Name</i> and click on <i>Register</i>.
				</li>
				<li>In a different machine (or a different tab in the same
					browser) follow the same procedure to register another user.</li>
				<li>Type the name of the user to be called in the field <i>Peer</i>
					and click on <i>Call</i>.
				</li>
				<li>Grant the access to the camera and microphone for both
					users. After the SDP negotiation the communication should start.</li>
				<li>The called user should accept the incoming call (by a
					confirmation dialog).</li>
				<li>Click on <i>Stop</i> to finish the communication.
				</li>
				<li>Type the name of the user to play its recording in the
					field <i>Peer</i> and click on <i>Play Rec</i>
				</li>
			</ol>
		</div>
		<div class="row">
			<div class="col-md-5">
				<label class="control-label" for="name">Name</label>
				<div class="row">
					<div class="col-md-5">
						<input id="name" name="name" class="form-control" type="text"
							onkeydown="if (event.keyCode == 13) register();" />
					</div>
					<div class="col-md-7 text-right">
						<a id="register" href="#" class="btn btn-primary"><span
							class="glyphicon glyphicon-plus"></span> Register</a>
					</div>
				</div>

				<br /> <br /> <label class="control-label" for="peer">Peer</label>
				<div class="row">
					<div class="col-md-5">
						<input id="peer" name="peer" class="form-control" type="text"
							onkeydown="if (event.keyCode == 13) call();">
					</div>
					<div class="col-md-7 text-right">
						<a id="call" href="#" class="btn btn-success"><span
							class="glyphicon glyphicon-play"></span> Call</a> <a id="terminate"
							href="#" class="btn btn-danger"><span
							class="glyphicon glyphicon-stop"></span> Stop</a> <a id="play"
							href="#" class="btn btn-warning"><span
							class="glyphicon glyphicon-play-circle"></span>Play Rec</a>

					</div>
				</div>
				<br /> <label class="control-label" for="console">Console</label><br>
				<br>
				<div id="console" class="democonsole">
					<ul></ul>
				</div>
			</div>
			<div class="col-md-7">
				<div id="videoBig">
					<video id="videoOutput" autoplay width="640px" height="480px"
						poster="./img/webrtc.png"></video>
				</div>
				<div id="videoSmall">
					<video id="videoInput" autoplay width="240px" height="180px"
						poster="./img/webrtc.png"></video>
				</div>
			</div>
		</div>
	</div>

	<footer>
		<div class="foot-fixed-bottom">
			<div class="container text-center">
				<hr />
				<div class="row">&copy; 2014-2015 Kurento</div>
				<div class="row">
					<div class="col-md-4">
						<a href="http://www.urjc.es"><img src="./img/urjc.gif"
							alt="Universidad Rey Juan Carlos" height="50px" /></a>
					</div>
					<div class="col-md-4">
						<a href="http://www.kurento.org"><img src="./img/kurento.png"
							alt="Kurento" height="50px" /></a>
					</div>
					<div class="col-md-4">
						<a href="http://www.naevatec.com"><img
							src="./img/naevatec.png" alt="Naevatec" height="50px" /></a>
					</div>
				</div>
			</div>
		</div>
	</footer>
</body>
</html>

one2one.js



var ws = new WebSocket('ws://' + location.host + '/call');
var videoInput;
var videoOutput;
var webRtcPeer;
var from;

var registerName = null;
var registerState = null;
const NOT_REGISTERED = 0;
const REGISTERING = 1;
const REGISTERED = 2;

function setRegisterState(nextState) {
	switch (nextState) {
	case NOT_REGISTERED:
		enableButton('#register', 'register()');
		setCallState(DISABLED);
		break;
	case REGISTERING:
		disableButton('#register');
		break;
	case REGISTERED:
		disableButton('#register');
		setCallState(NO_CALL);
		break;
	default:
		return;
	}
	registerState = nextState;
}

var callState = null;
const NO_CALL = 0;
const IN_CALL = 1;
const POST_CALL = 2;
const DISABLED = 3;
const IN_PLAY = 4;

function setCallState(nextState) {
	switch (nextState) {
	case NO_CALL:
		enableButton('#call', 'call()');
		disableButton('#terminate');
		disableButton('#play');
		break;
	case DISABLED:
		disableButton('#call');
		disableButton('#terminate');
		disableButton('#play');
		break;
	case POST_CALL:
		enableButton('#call', 'call()');
		disableButton('#terminate');
		enableButton('#play', 'play()');
		break;
	case IN_CALL:
	case IN_PLAY:
		disableButton('#call');
		enableButton('#terminate', 'stop()');
		disableButton('#play');
		break;
	default:
		return;
	}
	callState = nextState;
}

function disableButton(id) {
	$(id).attr('disabled', true);
	$(id).removeAttr('onclick');
}

function enableButton(id, functionName) {
	$(id).attr('disabled', false);
	$(id).attr('onclick', functionName);
}

window.onload = function() {
	console = new Console();
	setRegisterState(NOT_REGISTERED);
	var drag = new Draggabilly(document.getElementById('videoSmall'));
	videoInput = document.getElementById('videoInput');
	videoOutput = document.getElementById('videoOutput');
	document.getElementById('name').focus();
}

window.onbeforeunload = function() {
	ws.close();
}

ws.onmessage = function(message) {
	var parsedMessage = JSON.parse(message.data);
	console.info('Received message: ' + message.data);

	switch (parsedMessage.id) {
	case 'registerResponse':
		registerResponse(parsedMessage);
		break;
	case 'callResponse':
		callResponse(parsedMessage);
		break;
	case 'incomingCall':
		incomingCall(parsedMessage);
		break;
	case 'startCommunication':
		startCommunication(parsedMessage);
		break;
	case 'stopCommunication':
		console.info('Communication ended by remote peer');
		stop(true);
		break;
	case 'playResponse':
		playResponse(parsedMessage);
		break;
	case 'playEnd':
		playEnd();
		break;
	case 'iceCandidate':
		webRtcPeer.addIceCandidate(parsedMessage.candidate, function(error) {
			if (error)
				return console.error('Error adding candidate: ' + error);
		});
		break;
	default:
		console.error('Unrecognized message', parsedMessage);
	}
}

function registerResponse(message) {
	if (message.response == 'accepted') {
		setRegisterState(REGISTERED);
		document.getElementById('peer').focus();
	} else {
		setRegisterState(NOT_REGISTERED);
		var errorMessage = message.response ? message.response
				: 'Unknown reason for register rejection.';
		console.log(errorMessage);
		document.getElementById('name').focus();
		alert('Error registering user. See console for further information.');
	}
}

function callResponse(message) {
	if (message.response != 'accepted') {
		console.info('Call not accepted by peer. Closing call');
		stop();
		setCallState(NO_CALL);
		if (message.message) {
			alert(message.message);
		}
	} else {
		setCallState(IN_CALL);
		webRtcPeer.processAnswer(message.sdpAnswer, function(error) {
			if (error)
				return console.error(error);
		});
	}
}

function startCommunication(message) {
	setCallState(IN_CALL);
	webRtcPeer.processAnswer(message.sdpAnswer, function(error) {
		if (error)
			return console.error(error);
	});
}

function playResponse(message) {
	if (message.response != 'accepted') {
		hideSpinner(videoOutput);
		document.getElementById('videoSmall').style.display = 'block';
		alert(message.error);
		document.getElementById('peer').focus();
		setCallState(POST_CALL);
	} else {
		setCallState(IN_PLAY);
		webRtcPeer.processAnswer(message.sdpAnswer, function(error) {
			if (error)
				return console.error(error);
		});
	}
}

function incomingCall(message) {
	// If bussy just reject without disturbing user
	if (callState != NO_CALL && callState != POST_CALL) {
		var response = {
			id : 'incomingCallResponse',
			from : message.from,
			callResponse : 'reject',
			message : 'bussy'
		};
		return sendMessage(response);
	}

	setCallState(DISABLED);
	if (confirm('User ' + message.from
			+ ' is calling you. Do you accept the call?')) {
		showSpinner(videoInput, videoOutput);

		from = message.from;
		var options = {
			localVideo : videoInput,
			remoteVideo : videoOutput,
			onicecandidate : onIceCandidate
		}
		webRtcPeer = new kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options,
				function(error) {
					if (error) {
						return console.error(error);
					}
					this.generateOffer(onOfferIncomingCall);
				});
	} else {
		var response = {
			id : 'incomingCallResponse',
			from : message.from,
			callResponse : 'reject',
			message : 'user declined'
		};
		sendMessage(response);
		stop();
	}
}

function onOfferIncomingCall(error, offerSdp) {
	if (error)
		return console.error('Error generating the offer ' + error);
	var response = {
		id : 'incomingCallResponse',
		from : from,
		callResponse : 'accept',
		sdpOffer : offerSdp
	};
	sendMessage(response);
}

function register() {
	var name = document.getElementById('name').value;
	if (name == '') {
		window.alert('You must insert your user name');
		document.getElementById('name').focus();
		return;
	}
	setRegisterState(REGISTERING);

	var message = {
		id : 'register',
		name : name
	};
	sendMessage(message);
}

function call() {
	if (document.getElementById('peer').value == '') {
		document.getElementById('peer').focus();
		window.alert('You must specify the peer name');
		return;
	}
	setCallState(DISABLED);
	showSpinner(videoInput, videoOutput);

	var options = {
		localVideo : videoInput,
		remoteVideo : videoOutput,
		onicecandidate : onIceCandidate
	}
	webRtcPeer = new kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options,
			function(error) {
				if (error) {
					return console.error(error);
				}
				this.generateOffer(onOfferCall);
			});
}

function onOfferCall(error, offerSdp) {
	if (error)
		return console.error('Error generating the offer ' + error);
	console.log('Invoking SDP offer callback function');
	var message = {
		id : 'call',
		from : document.getElementById('name').value,
		to : document.getElementById('peer').value,
		sdpOffer : offerSdp
	};
	sendMessage(message);
}

function play() {
	var peer = document.getElementById('peer').value;
	if (peer == '') {
		window
				.alert("You must insert the name of the user recording to be played (field 'Peer')");
		document.getElementById('peer').focus();
		return;
	}

	document.getElementById('videoSmall').style.display = 'none';
	setCallState(DISABLED);
	showSpinner(videoOutput);

	var options = {
		remoteVideo : videoOutput,
		onicecandidate : onIceCandidate
	}
	webRtcPeer = new kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options,
			function(error) {
				if (error) {
					return console.error(error);
				}
				this.generateOffer(onOfferPlay);
			});
}

function onOfferPlay(error, offerSdp) {
	console.log('Invoking SDP offer callback function');
	var message = {
		id : 'play',
		user : document.getElementById('peer').value,
		sdpOffer : offerSdp
	};
	sendMessage(message);
}

function playEnd() {
	setCallState(POST_CALL);
	hideSpinner(videoInput, videoOutput);
	document.getElementById('videoSmall').style.display = 'block';
}

function stop(message) {
	var stopMessageId = (callState == IN_CALL) ? 'stop' : 'stopPlay';
	setCallState(POST_CALL);
	if (webRtcPeer) {
		webRtcPeer.dispose();
		webRtcPeer = null;

		if (!message) {
			var message = {
				id : stopMessageId
			}
			sendMessage(message);
		}
	}
	hideSpinner(videoInput, videoOutput);
	document.getElementById('videoSmall').style.display = 'block';
}

function sendMessage(message) {
	var jsonMessage = JSON.stringify(message);
	console.log('Senging message: ' + jsonMessage);
	ws.send(jsonMessage);
}

function onIceCandidate(candidate) {
	console.log('Local candidate ' + JSON.stringify(candidate));

	var message = {
		id : 'onIceCandidate',
		candidate : candidate
	};
	sendMessage(message);
}

function showSpinner() {
	for (var i = 0; i < arguments.length; i++) {
		arguments[i].poster = './img/transparent-1px.png';
		arguments[i].style.background = 'center transparent url("./img/spinner.gif") no-repeat';
	}
}

function hideSpinner() {
	for (var i = 0; i < arguments.length; i++) {
		arguments[i].src = '';
		arguments[i].poster = './img/webrtc.png';
		arguments[i].style.background = '';
	}
}

/**
 * Lightbox utility (to display media pipeline image in a modal dialog)
 */
$(document).delegate('*[data-toggle="lightbox"]', 'click', function(event) {
	event.preventDefault();
	$(this).ekkoLightbox();
});

adapter.js

(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/*
 *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
 /* eslint-env node */
'use strict';

var SDPUtils = require('sdp');

function fixStatsType(stat) {
  return {
    inboundrtp: 'inbound-rtp',
    outboundrtp: 'outbound-rtp',
    candidatepair: 'candidate-pair',
    localcandidate: 'local-candidate',
    remotecandidate: 'remote-candidate'
  }[stat.type] || stat.type;
}

function writeMediaSection(transceiver, caps, type, stream, dtlsRole) {
  var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);

  // Map ICE parameters (ufrag, pwd) to SDP.
  sdp += SDPUtils.writeIceParameters(
      transceiver.iceGatherer.getLocalParameters());

  // Map DTLS parameters to SDP.
  sdp += SDPUtils.writeDtlsParameters(
      transceiver.dtlsTransport.getLocalParameters(),
      type === 'offer' ? 'actpass' : dtlsRole || 'active');

  sdp += 'a=mid:' + transceiver.mid + '\r\n';

  if (transceiver.rtpSender && transceiver.rtpReceiver) {
    sdp += 'a=sendrecv\r\n';
  } else if (transceiver.rtpSender) {
    sdp += 'a=sendonly\r\n';
  } else if (transceiver.rtpReceiver) {
    sdp += 'a=recvonly\r\n';
  } else {
    sdp += 'a=inactive\r\n';
  }

  if (transceiver.rtpSender) {
    var trackId = transceiver.rtpSender._initialTrackId ||
        transceiver.rtpSender.track.id;
    transceiver.rtpSender._initialTrackId = trackId;
    // spec.
    var msid = 'msid:' + (stream ? stream.id : '-') + ' ' +
        trackId + '\r\n';
    sdp += 'a=' + msid;
    // for Chrome. Legacy should no longer be required.
    sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
        ' ' + msid;

    // RTX
    if (transceiver.sendEncodingParameters[0].rtx) {
      sdp += 'a
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值