文档章节

Kurento应用开发指南(以Kurento 5.0为模板) 之三:示例教程 一对多的视频呼叫

小马_wolf
 小马_wolf
发布于 2016/07/14 14:55
字数 2311
阅读 119
收藏 0
点赞 0
评论 0

5.2  教程3一对多的视频呼叫

这个示例页面应用程序使用WebRTC技术实现了一对多的视频呼叫。换句话说,它是一个基于页面的视频广播应用。
 

5.2.1 运行示例程序

运行这个DEMO之前,需要先安装 Kurento Media Server. 另外,还需要先安装JDK (at least version 7), Maven, Git, 及Bower。
Nodejs及bower的安装指令如下: 
# sudo apt-get install curl
# curl -sL https://deb.nodesource.com/setup | sudo bash -
# sudo apt-get install -y nodejs
# sudo npm install -g bower


示例代码需要先从项目的GitHub上下载并编译运行:
# git clone https://github.com/Kurento/kurento-tutorial-java.git
# cd kurento-tutorial-java/kurento-one2many-call
# mvn clean compile exec:java


此时,应用程序已在8080端口上启动,在兼容WebRTC的浏览器 (Chrome, Firefox)上输入网址:
http://localhost:8080/ 

 

5.2.2  理解这个示例程序

在这个应用程序中,有两种类型的用户:
一个人负责发送媒体,称作Master, 
N个人从Master上接收媒体,称作Viewer。
因此,媒体管道由1+N 个 WebRtcEndpoints互联组成,下图显示了Master的页面截图:
 
Figure 8.1: One to many video call screenshot


为了实现上述的动作,需要先创建一个由1+N WebRtcEndpoints 组成的媒体管道。
Master端发送它的流给其它的Viewers。Viewer配置成只接收模式。
媒体管道的示例图示如下:
 
Figure 8.2: One to many video call Media Pipeline


这是一个页面应用程序,因此它使用的是客户-服务端架构。
在客户端,它的逻辑是由JavaScript实现的。
在服务端,它使用Kurento Java Client以到达Kurento Media Server。
总而言之,这个DEMO的高层架构是一个三层结构,为了实现这些实体间的通信,需要使用两个WebSocket: 
首先,一个WebSocket建立在客户端与服务端之间,以实现一个定制化的信令协议。
其次,另一个WebSocket用来实现Kurento Java Client和 Kurento Media Server间的通信,这个通信是由Kurento Protocol实现的。

客户端与应用服务端的通信使用的是基于WebSocket,使用JSON消息实现的信令协议。
客户端与服务端的工作逻辑如下:
1. Master进入系统,在任何时候,有且仅有一个Master。
因此,如果Master已存在,在另一个用户尝试成为Master时会报出差信息。
2. N个Viewer连接到master,如果系统中没有master, 那么Viewer将会收到相应的出错信息。
3. Viewer可以在任何时候离开这次通信。
4. 当Master结束这次会话时,那么每个连接的Viewer都会收到一个StopCommunication消息并结束这次会话;


下面的时序图显示了客户端与服务端消息传递的细节。
如图所示,客户端与服务端为了在浏览器和Kurento之间建立WebRTC连接,需要使用SDP数据交换。
另外,SDP协商连接了浏览器上的  WebRtcPeer 与服务器上的WebRtcEndpoint。完整的源码见GibHub;


 
Figure 8.3: One to many video call signaling protocol


5.2.3 应用程序服务端逻辑
这个DEMO的服务端使用Java的Spring Boot框架实现,这个技术可以被嵌入到Tomcat页面服务器中以简化开发过程。
Note: 
你可以使用任何你喜欢的Java服务端技术来创建基于kurento的页面应用。
例如,纯粹的Java EE应用,SIP Servlets, Play, Vertex等。我们通常选择Spring Boot框架。


下面的源码中可以看到服务端代码的类视图:
DEMO中的主类命名为 One2ManyCallApp,  
KurentoClient在这个类中的实例是作为一个Spring Bean, 这个Bean用来创建 Kurento 媒体管道,
它可以用来给应用程序添加媒体能力。
在这个实例中,我们可以看到WebSocket被用来连接Kurento Media Server,
默认地,在本机上,它监听8888端口。
源码见:
src/main/java/org/kurento/tutorial/one2manycall/One2ManyCallApp.java 


@Configuration
@EnableWebSocket
@EnableAutoConfiguration
public class One2ManyCallApp implements WebSocketConfigurer {
    @Bean
    public CallHandler callHandler() {
        return new CallHandler();
    }


    @Bean
    public KurentoClient kurentoClient() {
        return KurentoClient.create("ws://localhost:8888/kurento");
    }
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(callHandler(), "/call");
    }
    public static void main(String[] args) throws Exception {
        new SpringApplication(One2ManyCallApp.class).run(args);
    }
}


 


Figure 8.4: Server-side class diagram of the MagicMirror app


这个页面应用程序使用了单页面应用程序架构(SPA:Single Page Application architecture ),
并使用了WebSocket来作为客户端与服务端通信的请求与响应。
特别地,主app类实现了WebSocketConfigurer接口来注册一个WebSocketHandler来处理WebSocket请求。


CallHandler类实现了TextWebSocketHandler,用来处理文本WebSocket的请求。
这个类的主要实现的方法就是handleTextMessage, 这个方法实现了对请求的动作: 
通过WebSocket返回对请求的响应。换句话说,它实现前面的时序图中的信令协议的服务端部分。
在设计的协议中,有三种类型的输入消息:master, viewer和stop。
这些消息对应的处理都在switch中;
源码见:
src/main/java/org/kurento/tutorial/one2manycall/CallHandler.java


public class CallHandler extends TextWebSocketHandler {
    private static final Logger log = LoggerFactory.getLogger(CallHandler.class);
    private static final Gson gson = new GsonBuilder().create();
    private ConcurrentHashMap<String, UserSession> viewers =
    new ConcurrentHashMap<String, UserSession>();


    @Autowired
    private KurentoClient kurento;
    private MediaPipeline pipeline;
    private UserSession masterUserSession;


    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message)
    throws Exception {
        JsonObject jsonMessage = gson.fromJson(message.getPayload(), JsonObject.class);
        log.debug("Incoming message from session '{}': {}", session.getId(), jsonMessage);
        switch (jsonMessage.get("id").getAsString()) {
        case "master":
            try {
                master(session, jsonMessage);
            } catch (Throwable t) {
                stop(session);
                log.error(t.getMessage(), t);
                JsonObject response = new JsonObject();
                response.addProperty("id", "masterResponse");
                response.addProperty("response", "rejected");
                response.addProperty("message", t.getMessage());
                session.sendMessage(new TextMessage(response.toString()));
            }
        break;
        case "viewer":
            try {
                viewer(session, jsonMessage);
            } catch (Throwable t) {
                stop(session);
                log.error(t.getMessage(), t);
                JsonObject response = new JsonObject();
                response.addProperty("id", "viewerResponse");
                response.addProperty("response", "rejected");
                response.addProperty("message", t.getMessage());
                session.sendMessage(new TextMessage(response.toString()));
            }
        break;
        case "stop":
            stop(session);
        break;
        default:
        break;
    }
}
private synchronized void master(WebSocketSession session,
    JsonObject jsonMessage) throws IOException {
        ...
}
private synchronized void viewer(WebSocketSession session,
    JsonObject jsonMessage) throws IOException {
        ...
}
private synchronized void stop(WebSocketSession session) throws IOException {
    ...
}
@Override
public void afterConnectionClosed(WebSocketSession session,
    CloseStatus status) throws Exception {
        stop(session);
    }
}


下面的代码片断中,可以看到master方法,它为master创建了一个Media管道和WebRtcEndpoint:
private synchronized void master(WebSocketSession session,
    JsonObject jsonMessage) throws IOException {
        if (masterUserSession == null) {
            masterUserSession = new UserSession(session);
            pipeline = kurento.createMediaPipeline();
            masterUserSession.setWebRtcEndpoint(new WebRtcEndpoint.Builder(pipeline).build());
            WebRtcEndpoint masterWebRtc = masterUserSession.getWebRtcEndpoint();
            String sdpOffer = jsonMessage.getAsJsonPrimitive("sdpOffer").getAsString();
            String sdpAnswer = masterWebRtc.processOffer(sdpOffer);
            JsonObject response = new JsonObject();
            response.addProperty("id", "masterResponse");
            response.addProperty("response", "accepted");
            response.addProperty("sdpAnswer", sdpAnswer);
            masterUserSession.sendMessage(response);
        } else {
            JsonObject response = new JsonObject();
            response.addProperty("id", "masterResponse");
            response.addProperty("response", "rejected");
            response.addProperty("message",
            "Another user is currently acting as sender. Try again later ...");
            session.sendMessage(new TextMessage(response.toString()));
        }
}


The viewer method is similar, but not he Master WebRtcEndpoint is 
connected to each of the viewers WebRtcEndpoints,otherwise an error is sent back to the client.
viewer方法也是类似的,但
private synchronized void viewer(WebSocketSession session,
        JsonObject jsonMessage) throws IOException {
    if (masterUserSession == null || masterUserSession.getWebRtcEndpoint() == null) {
        JsonObject response = new JsonObject();
        response.addProperty("id", "viewerResponse");
        response.addProperty("response", "rejected");
        response.addProperty("message",
        "No active sender now. Become sender or . Try again later ...");
        session.sendMessage(new TextMessage(response.toString()));
    } else {
        if(viewers.containsKey(session.getId())){
            JsonObject response = new JsonObject();
            response.addProperty("id", "viewerResponse");
            response.addProperty("response", "rejected");
            response.addProperty("message",
                "You are already viewing in this session. " +
                "Use a different browser to add additional viewers.");
            session.sendMessage(new TextMessage(response.toString()));
            return;
        }
        UserSession viewer = new UserSession(session);
        viewers.put(session.getId(), viewer);
        String sdpOffer = jsonMessage.getAsJsonPrimitive("sdpOffer").getAsString();
        WebRtcEndpoint nextWebRtc = new WebRtcEndpoint.Builder(pipeline).build();
        viewer.setWebRtcEndpoint(nextWebRtc);
        masterUserSession.getWebRtcEndpoint().connect(nextWebRtc);
        String sdpAnswer = nextWebRtc.processOffer(sdpOffer);
        JsonObject response = new JsonObject();
        response.addProperty("id", "viewerResponse");
        response.addProperty("response", "accepted");
        response.addProperty("sdpAnswer", sdpAnswer);
        viewer.sendMessage(response);
    }
}


最后,stop消息结束通信。如果这个消息是由master发送的,则stopCommunication消息将发送到每个连接的观看端:
private synchronized void stop(WebSocketSession session) throws IOException {
    String sessionId = session.getId();
    if (masterUserSession != null
            && masterUserSession.getSession().getId().equals(sessionId)) {
        for (UserSession viewer : viewers.values()) {
            JsonObject response = new JsonObject();
            response.addProperty("id", "stopCommunication");
            viewer.sendMessage(response);
        }
        log.info("Releasing media pipeline");
        if (pipeline != null) {
            pipeline.release();
        }
        pipeline = null;
        masterUserSession = null;
    } else if (viewers.containsKey(sessionId)) {
        if (viewers.get(sessionId).getWebRtcEndpoint() != null) {
            viewers.get(sessionId).getWebRtcEndpoint().release();
        }
        viewers.remove(sessionId);
    }
}

 

5.2.4  客户端

现在来看应用程序的客户端,为了呼叫前面在服务端创建的WebSocket服务,我们使用了JavaScript类WebSocket。
我们使用了一个特殊的Kurento JavaScripty库,叫做 kurento-utils.js, 来简化和服务端的WebRTC交互。
这个库依赖于 adapter.js, 它是一个JavaScript WebRTC utility,由Google管理,它抽象了浏览器之间的差异。
最后,jquery.js在这个应用中也同样需要;


这些库都链接到了index.html页面,并在index.js中被使用。
在下面的代码片断中,我们可以看到在路径 /call下创建了WebSocket(变量 ws)。
然后,WebSocket的监听者onmessage用于在客户端实现JSON信令协议。
这里有四种输入消息给客户端:
masterResponse, viewerResponse, 和 stopCommunication。
这些动作都是用来实现通信中的每个步骤。
例如,在master函数中,Kurento-utils.js的函数WebRtcPeer.startSendRecv是用来启动WebRTC通信。
然后,WebRtcPeer.startRecvOnly在viewer函数中被使用。


var ws = new WebSocket('ws://' + location.host + '/call');
ws.onmessage = function(message) {
    var parsedMessage = JSON.parse(message.data);
    console.info('Received message: ' + message.data);


    switch (parsedMessage.id) {
    case 'masterResponse':
        masterResponse(parsedMessage);
    break;
    case 'viewerResponse':
        viewerResponse(parsedMessage);
    break;
    case 'stopCommunication':
        dispose();
    break;
    default:
        console.error('Unrecognized message', parsedMessage);
    }
}
function master() {
    if (!webRtcPeer) {
        showSpinner(videoInput, videoOutput);
        webRtcPeer = kurentoUtils.WebRtcPeer.startSendRecv(videoInput, videoOutput,
        function(offerSdp) {
            var message = {
                id : 'master',
                sdpOffer : offerSdp
            };
            sendMessage(message);
        });
    }
}
function viewer() {
    if (!webRtcPeer) {
        document.getElementById('videoSmall').style.display = 'none';
        showSpinner(videoOutput);
        webRtcPeer = kurentoUtils.WebRtcPeer.startRecvOnly(videoOutput, function(offerSdp) {
        var message = {
            id : 'viewer',
            sdpOffer : offerSdp
        };
        sendMessage(message);
    });
}
}

 

5.2.5 依赖库

这个Java Spring 应用使用Maven实现。在pom.xml中声明了Kurento依赖库。
如下面的代码片断所示,我们需要两个依赖库:
Kurento Client Java 依赖库(kurento-client)和
用于客户端的JavaScript Kurento utility库(kurento-utils)
<dependencies>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-client</artifactId>
<version>[5.0.0,6.0.0)</version>
</dependency>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-utils-js</artifactId>
<version>[5.0.0,6.0.0)</version>
</dependency>
</dependencies>


Kurento framework uses Semantic Versioning for releases. Notice that range [5.0.0,6.0.0) 
downloads the latest version of Kurento artefacts from Maven Central in version 5 (i.e. 5.x.x). 
Major versions are released when incompatible changes are made.
Kurento框架使用了语义化版本号发布。
Note: We are in active development. You can find the latest version of Kurento Java Client at Maven Central.
Kurento Java Client has a minimum requirement of Java 7. To configure the application to use Java 7, 
we have to include the following properties in the properties section:
<maven.compiler.target>1.7</maven.compiler.target>
<maven.compiler.source>1.7</maven.compiler.source>

本文转载自:http://blog.chinaunix.net/uid-26000296-id-5159321.html

共有 人打赏支持
小马_wolf
粉丝 4
博文 43
码字总数 25915
作品 0
朝阳
程序员
Kurento应用开发指南(以Kurento 5.0为模板) 目录

Kurento是什么 3 Kurento简介 3 2.1 WebRTC媒体服务器 3 2.2 Kurento 媒体服务器 4 2.3 Kurento API, 客户端和协议 5 2.4 使用Kurento创建应用程序 7 Kurento Media Server的安装与卸载 8 3....

小马_wolf ⋅ 2016/07/14 ⋅ 0

Kurento应用开发指南(以Kurento 5.0为模板) 之五:kurento架构

Kurento 架构 和大多数的媒体通信技术一样,Kurento把所有的交互通信系统的关键功能抽象成两层(或平台): ?信令平台 系统中负责通信管理的部分,它的组成模块提供的功能有媒体协商,QoS参数协...

小马_wolf ⋅ 2016/07/14 ⋅ 0

Kurento应用开发指南(以Kurento 5.0为模板) 之四:示例教程 一对一视频呼叫

5.3 教程四-一对一的视频呼叫 这个页面应用程序使用WebRTC技术实现了一个一对一的呼叫,换言话说,这个应用提供了一个简单的视频电话 5.3.1 运行示例程序 运行这个DEMO之前,你需要先安装Kur...

小马_wolf ⋅ 2016/07/14 ⋅ 0

Kurento应用开发指南(以Kurento 5.0为模板) 之二:示例教程helloworld

Kurento教程 这部分包含了如何使用Kurento框架的教程,以创建不同类型WebRTC和多媒体应用。 教程包含下面三个方面: ? Java: These show applications where clients interact with an applic...

小马_wolf ⋅ 2016/07/14 ⋅ 0

Kurento应用开发指南(以Kurento 5.0为模板) 之一:简介,安装与卸载

Kurento是什么 Kurento是一个WebRTC媒体服务器,并且包含一个客户端API集合, 用以简化WWW和移动平台上的高级视频应用程序的开发。 Kurento的功能包括组通信,转码,记录,混音,广播和routi...

小马_wolf ⋅ 2016/07/14 ⋅ 0

WebRTC入门学习之初识WebRTC

引言: 先声明本人只是小小实习生一枚,若有不正确的,希望大家帮忙指正。 一、WebRTC基本架构 图一 WebRTC总体架构,摘自百度百科 先说说WebRTC大致的实现思路:我们创建的web app,然后在a...

小小实习生 ⋅ 2015/08/08 ⋅ 0

WebRTC流媒体服务器--Kurento

Kurento 是一个 WebRTC 流媒体服务器以及一些客户端API,有了它,开发WWW及智能手机平台的高级视频应用就变得更加容易。可以利用Kurento开发的应用类型包括,视频会议,音视频广播,音视频录...

dqzhangp ⋅ 2014/11/14 ⋅ 0

kurento开发android视频客户端

最近在搞kurento,流媒体服务。现在基于kurento进行开发,实现ios,android,web视频通话。目前web版本,ios版本都已开发完成,正常使用。但是android版本遇到问题,基于 google最 新的webrtc ...

wangjie142 ⋅ 2016/08/19 ⋅ 1

我所了解的webRTC是怎样的一种技术

通过浏览器对视频进行快速、安全的传输不用借助任何第三方平台应用可以实现吗 根据你的需求,不止一种方式可以在你的网站里添加WebRTC WebRTC(Web Real-Time Communication)是一项开源技术...

丁永 ⋅ 2016/01/18 ⋅ 2

kurento学习进阶一(源码库依赖关系)

Developer Guide¶ This section is a comprehensive guide for development of Kurento itself. The intended reader of this text is any person who wants to get involved in writing c......

liuweihui521 ⋅ 04/13 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

005. 深入JVM学习—Java堆内存参数调整

1. JVM整体内存调整图解(调优关键) 实际上每一块子内存区域都会存在一部分可变伸缩区域,其基本流程:如果内存空间不足,则在可变的范围之内扩大内存空间,当一段时间之后,内存空间不紧张...

影狼 ⋅ 17分钟前 ⋅ 0

内存障碍: 软件黑客的硬件视图

此文为笔者近日有幸看到的一则关于计算机底层内存障碍的学术论文,并翻译(机译)而来[自认为翻译的还行],若读者想要英文原版的论文话,给我留言,我发给你。 内存障碍: 软件黑客的硬件视图...

Romane ⋅ 50分钟前 ⋅ 0

SpringCloud 微服务 (七) 服务通信 Feign

壹 继续第(六)篇RestTemplate篇 做到现在,本机上已经有注册中心: eureka, 服务:client、order、product 继续在order中实现通信向product服务,使用Feign方式 下面记录学习和遇到的问题 贰 or...

___大侠 ⋅ 今天 ⋅ 0

gitee、github上issue标签方案

目录 [TOC] issue生命周期 st=>start: 开始e=>end: 结束op0=>operation: 新建issueop1=>operation: 评审issueop2=>operation: 任务负责人执行任务cond1=>condition: 是否通过?op3=>o......

lovewinner ⋅ 今天 ⋅ 0

浅谈mysql的索引设计原则以及常见索引的区别

索引定义:是一个单独的,存储在磁盘上的数据库结构,其包含着对数据表里所有记录的引用指针. 数据库索引的设计原则: 为了使索引的使用效率更高,在创建索引时,必须考虑在哪些字段上创建索...

屌丝男神 ⋅ 今天 ⋅ 0

String,StringBuilder,StringBuffer三者的区别

这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。 首先说运行速度,或者说是, 1.执行速度 在这方面运行速度快慢为:StringBuilder(线程不安全,可变) > StringBuffer...

时刻在奔跑 ⋅ 今天 ⋅ 0

java以太坊开发 - web3j使用钱包进行转账

首先载入钱包,然后利用账户凭证操作受控交易Transfer进行转账: Web3j web3 = Web3j.build(new HttpService()); // defaults to http://localhost:8545/Credentials credentials = Wallet......

以太坊教程 ⋅ 今天 ⋅ 0

Oracle全文检索配置与实践

Oracle全文检索配置与实践

微小宝 ⋅ 今天 ⋅ 0

mysql的分区和分表

1,什么是mysql分表,分区 什么是分表,从表面意思上看呢,就是把一张表分成N多个小表,具体请看mysql分表的3种方法 什么是分区,分区呢就是把一张表的数据分成N多个区块,这些区块可以在同一...

梦梦阁 ⋅ 今天 ⋅ 0

exception.ZuulException: Forwarding error

错误日志 com.netflix.zuul.exception.ZuulException: Forwarding error Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: xxx timed-out and no fallback available. Ca......

jack_peng ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部