Kurento Java Tutorial - Hello World

Kurento Java Tutorial - Hello World

這個web應用程序是爲Java開發人員介紹用Kurento編程的原則而設計的。它包含一個WebRTC鏡像視頻通信(環回)。本教程假設您具備Java、JavaScript、HTML和WebRTC的基本知識。在開始本教程之前,我們還建議閱讀關於Kurento和WebRTC的部分。

註釋

此應用程序使用HTTPS。如果您在本地主機中運行它並在瀏覽器中接受安全異常,它將正常工作,但是如果遠程運行,您應該保護您的應用程序。有關更多信息,請選中“配置Java服務器以使用HTTPS”。

快速開始

跟着下面的步驟運行演示程序:
1、安裝Kurento Media Server: 安裝嚮導鏈接
2、運行這些命令:

git clone https://github.com/Kurento/kurento-tutorial-java.git
cd kurento-tutorial-java/kurento-hello-world
git checkout 6.13.0
mvn -U clean spring-boot:run -Dkms.url=ws://localhost:8888/kurento

3、使用WebRTC兼容瀏覽器(Chrome、Firefox)打開演示頁:https://localhost:8443/
4、單擊“開始”開始演示。
5、允許訪問您的網絡攝像頭。
6、一旦協商並建立環回連接,您就應該在本地和遠程佔位符中看到您的網絡攝像機視頻。
7、單擊“停止”完成演示。

理解這個例子

Kurento爲開發人員提供了一個Kurento Java客戶端來控制Kurento媒體服務器。這個客戶端庫可以用於任何類型的Java應用程序:服務器端Web、桌面、Android等。它與任何框架都兼容,如JavaEE、Spring、Play、Vert.x、Swing和JavaFX。

這個Hello World演示是使用Kurento可以創建的最簡單的web應用程序之一。下圖顯示了此演示運行的屏幕截圖:

在這裏插入圖片描述
Kurento Hello World Screenshot: WebRTC in loopback

應用程序的界面(一個HTML網頁)由兩個HTML5標記組成:一個顯示本地流(由設備網絡攝像頭捕獲),另一個顯示媒體服務器發送回客戶端的遠程流。

應用程序的邏輯非常簡單:本地流被髮送到Kurento媒體服務器,該服務器將其發送回客戶端,而無需修改。要實現此行爲,我們需要創建一個由單個媒體元素(即WebRtcEndpoint)組成的媒體管道,該管道具有交換全雙工(雙向)WebRTC媒體流的能力。此媒體元素連接到自身,以便將它從瀏覽器接收到的媒體發送回瀏覽器。此媒體管道如下圖所示:

在這裏插入圖片描述
這是一個web應用程序,因此它遵循客戶機-服務器體系結構。在客戶端,邏輯是用JavaScript實現的。在服務器端,我們使用基於Spring引導的應用服務器,使用Kurento Java客戶機API來控制Kurento媒體服務器的功能。總之,這個演示的頂層架構是三層的。爲了與這些實體通信,使用了兩個websocket:
1.在客戶端和應用服務器之間創建一個WebSocket來實現一個自定義的信令協議。
2、另一個WebSocket用於執行Kurento Java客戶端和Kurento媒體服務器之間的通信。
此通信使用Kurento協議進行。有關詳細說明,請閱讀本節:Kurento協議。

下圖顯示了與應用程序接口交互的完整序列圖:
i)JavaScript邏輯;
ii)應用服務器邏輯(使用Kurento Java客戶端);
iii)Kurento媒體服務器。在這裏插入圖片描述
Kurento Hello World(loopbak中的WebRTC)演示的完整序列圖

應用服務邏輯

這個演示是在服務器端使用Java開發的,基於Spring Boot框架,它在生成的maven工件中嵌入了Tomcat web服務器,從而簡化了開發和部署過程。

註釋
您可以使用您喜歡的任何Java服務器端技術來使用Kurento構建web應用程序。例如,一個純Java EE應用程序、SIP Servlets、Play、Vert.x等等。爲了方便起見,我們選擇了Spring Boot。

在下圖中,您可以看到服務器端代碼的類圖:

在這裏插入圖片描述
HelloWorld應用程序的服務器端類圖

這個演示的main class是HelloWorldApp。

如您所見,KurentoClient在這個類中被實例化爲一個Spring Bean。此bean用於創建Kurento媒體管道,用於嚮應用程序添加媒體功能。在這個實例中,我們看到需要向客戶機庫指定Kurento媒體服務器的位置。在這個例子中,我們假設它位於localhost,監聽端口8888。如果複製此示例,則需要在其中插入Kurento媒體服務器實例的特定位置。
一旦Kurento客戶端被實例化,就可以與Kurento媒體服務器通信並控制其多媒體功能。

@SpringBootApplication
@EnableWebSocket
public class HelloWorldApp implements WebSocketConfigurer {
  @Bean
  public HelloWorldHandler handler() {
    return new HelloWorldHandler();
  }

  @Bean
  public KurentoClient kurentoClient() {
    return KurentoClient.create();
  }

  @Override
  public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(handler(), "/helloworld");
  }

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

此web應用程序遵循單頁應用程序體系結構(SPA),並使用WebSocket通過請求和響應與應用服務器通信。具體來說,main app類實現接口WebSocketConfigurer以註冊一個WebSocketHandler,該WebSocketHandler處理 /helloworld 的WebSocket請求。

類HelloWorldHandler實現TextWebSocketHandler來處理文本WebSocket請求。這個類的中心部分是handleTextMessage方法。此方法實現請求的操作,通過WebSocket返回響應。換句話說,它實現了前面序列圖中描述的信令協議的服務器部分。

public void handleTextMessage(WebSocketSession session, TextMessage message)
    throws Exception {
  [...]
  switch (messageId) {
    case "start":
      start(session, jsonMessage);
      break;
    case "stop": {
      stop(session);
      break;
    }
    case "onIceCandidate":
      onRemoteIceCandidate(session, jsonMessage);
      break;
    default:
      sendError(session, "Invalid message, ID: " + messageId);
      break;
  }
  [...]
}

The start() method performs the following actions:

  • 配置媒體處理邏輯。這是應用程序配置Kurento如何處理媒體的部分。換句話說,媒體管道就是在這裏創建的。爲此,對象KurentoClient用於創建mediapipline對象。使用它,我們需要的媒體元素被創建和連接。在本例中,我們只實例化一個WebRtcEndpoint,用於接收WebRTC流並將其發送回客戶端。
final MediaPipeline pipeline = kurento.createMediaPipeline();

final WebRtcEndpoint webRtcEp =
    new WebRtcEndpoint.Builder(pipeline).build();

webRtcEp.connect(webRtcEp);
  • 創建事件偵聽器。由Kurento管理的所有對象都能夠發出幾種類型的事件,如端點事件中所述。應用服務器可以監聽它們,以便對媒體服務器的處理邏輯內部的情況有更多的瞭解。監聽所有可能的事件是一個很好的實踐,因此客戶機應用程序擁有儘可能多的信息。
// Common events for all objects that inherit from BaseRtpEndpoint
addErrorListener(
    new EventListener<ErrorEvent>() { ... });
addMediaFlowInStateChangeListener(
    new EventListener<MediaFlowInStateChangeEvent>() { ... });
addMediaFlowOutStateChangeListener(
    new EventListener<MediaFlowOutStateChangeEvent>() { ... });
addConnectionStateChangedListener(
    new EventListener<ConnectionStateChangedEvent>() { ... });
addMediaStateChangedListener(
    new EventListener<MediaStateChangedEvent>() { ... });
addMediaTranscodingStateChangeListener(
    new EventListener<MediaTranscodingStateChangeEvent>() { ... });

// Events specific to objects of class WebRtcEndpoint
addIceCandidateFoundListener(
    new EventListener<IceCandidateFoundEvent>() { ... });
addIceComponentStateChangeListener(
    new EventListener<IceComponentStateChangeEvent>() { ... });
addIceGatheringDoneListener(
    new EventListener<IceGatheringDoneEvent>() { ... });
addNewCandidatePairSelectedListener(
    new EventListener<NewCandidatePairSelectedEvent>() { ... });
  • WebRTC SDP協商。在WebRTC中,SDP提供/應答模型用於協商將在對等方之間交換的音頻或視頻軌跡,以及它們支持的公共特性的子集。此協商通過在一個對等方中生成SDP提議,將其發送給另一個對等方,並返回將生成的SDP響應來完成。
    在這種特殊情況下,SDP提議由瀏覽器生成併發送給Kurento,Kurento隨後生成SDP應答,該應答必須作爲響應發送回瀏覽器。
// 'webrtcSdpOffer' is the SDP Offer generated by the browser;
// send the SDP Offer to KMS, and get back its SDP Answer
String webrtcSdpAnswer = webRtcEp.processOffer(webrtcSdpOffer);
sendMessage(session, webrtcSdpAnswer);
  • Gather ICE candidates。當SDP提供/應答協商正在進行時,每個對等方都可以開始收集將用於ICE協議的連接候選。此過程的工作方式與瀏覽器通過發出事件RTCPeerConnection.onicecandidate通知其客戶機代碼的方式非常相似;同樣,Kurento的WebRtcEndpoint將通過事件IceCandidateFound通知其客戶機應用程序。
webRtcEp.gatherCandidates();

Client-Side Logic

現在讓我們轉到應用程序的客戶端。要在服務器端調用先前創建的WebSocket服務,我們使用JavaScript類WebSocket。我們使用一個名爲Kurento-utils.js的特定Kurento JavaScript庫來簡化WebRTC與服務器的交互。這個庫依賴於adapter.js,這是一個由Google維護的JavaScript WebRTC實用程序,它可以消除瀏覽器之間的差異。
這些庫作爲Maven依賴項引入到項目中,Maven依賴項從WebJars.org下載所有必需的文件;它們加載在index.html頁面中,並在index.js文件中使用。
在下面的代碼片段中,我們可以看到在path/helloworld中創建的WebSocket。然後,使用WebSocket的onmessage監聽器在客戶端實現JSON信令協議。請注意,有三條傳入客戶端的消息:startResponse、error和iceCandidate。爲實現通信中的每一步都採取了方便的措施。例如,在函數start中,kurento-utils.js的函數webrtcpeeer.WebRtcPeerSendrecv用於啓動WebRTC通信。

var ws = new WebSocket('ws://' + location.host + '/helloworld');

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

   switch (parsedMessage.id) {
   case 'startResponse':
      startResponse(parsedMessage);
      break;
   case 'error':
      if (state == I_AM_STARTING) {
         setState(I_CAN_START);
      }
      onError('Error message from server: ' + parsedMessage.message);
      break;
   case 'iceCandidate':
      webRtcPeer.addIceCandidate(parsedMessage.candidate, function(error) {
         if (error)
            return console.error('Error adding candidate: ' + error);
      });
      break;
   default:
      if (state == I_AM_STARTING) {
         setState(I_CAN_START);
      }
      onError('Unrecognized message', parsedMessage);
   }
}

function start() {
   console.log('Starting video call ...');

   // Disable start button
   setState(I_AM_STARTING);
   showSpinner(videoInput, videoOutput);

   console.log('Creating WebRtcPeer and generating local sdp offer ...');

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

function onOffer(error, offerSdp) {
   if (error)
      return console.error('Error generating the offer');
   console.info('Invoking SDP offer callback function ' + location.host);
   var message = {
      id : 'start',
      sdpOffer : offerSdp
   }
   sendMessage(message);
}

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

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

function startResponse(message) {
   setState(I_CAN_STOP);
   console.log('SDP answer received from server. Processing ...');

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

function stop() {
   console.log('Stopping video call ...');
   setState(I_CAN_START);
   if (webRtcPeer) {
      webRtcPeer.dispose();
      webRtcPeer = null;

      var message = {
         id : 'stop'
      }
      sendMessage(message);
   }
   hideSpinner(videoInput, videoOutput);
}

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

依賴項

這個Java Spring應用程序是使用Maven實現的。pom.xml的相關部分是聲明Kurento依賴項的地方。如下面的代碼片段所示,我們需要兩個依賴項:客戶端的Kurento客戶機Java依賴項(Kurento客戶機)和JavaScript Kurento實用程序庫(Kurento實用程序庫)。其他客戶端庫由WebJars管理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章