Kurento实战之五:媒体播放
Posted 程序员欣宸
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kurento实战之五:媒体播放相关的知识,希望对你有一定的参考价值。
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
- 本文是《Kurento实战》的第五篇,咱们用KMS的现有能力开发一个简单的媒体播放器,整体架构如下图:
- 从上图可见,实战主要内容是开发player-with-record应用,整个过程如下:
- 部署KMS
- 开发名为player-with-record的springboot应用,含简单的网页
- 浏览器打开网页,与player-with-record建立websocket连接,将流媒体地址发送到player-with-record
- player-with-record通过kurento SDK向KMS发指令,创建媒体播放和webrtc组件实例
- player-with-record还负责浏览器和前端页面之间的WebRTC信令传输
- 浏览器和KMS之前的媒体连接建立好之后,即可接收流媒体数据再播放出来
- 接下来进入实战,从部署KMS开始
源码下载
- 本篇实战中的完整源码可在GitHub下载到,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):
名称 | 链接 | 备注 |
---|---|---|
项目主页 | https://github.com/zq2599/blog_demos | 该项目在GitHub上的主页 |
git仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
- 这个git项目中有多个文件夹,本次实战的源码在kurentordemo文件夹下,如下图红框所示:
- kurentordemo是整个《Kurento实战》系列的父工程,里面有多个子工程,本篇对应的源码是子工程player-with-record,如下图红框:
部署KMS
- 为了简单操作,KMS还是采用docker的方式部署,执行如下命令即可:
docker run -d \\
--restart always \\
--name kms \\
--network host \\
kurento/kurento-media-server:6.15
- 和之前实战不同的是,KMS和player-with-record应用分别部署在不同的电脑上,因此,KMS所在机器记得关闭防火墙或者开放8888端口;
开发PlayerWithRecorder应用
- 在kurentodemo工程下,新增名为player-with-record的子工程,其pom.xml内容如下:
<?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>
<artifactId>kurentodemo</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.bolingcavalry</groupId>
<artifactId>player-with-record</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>player-with-record</name>
<packaging>jar</packaging>
<description>show how to play and record the file</description>
<!--不用spring-boot-starter-parent作为parent时的配置-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>jquery</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>demo-console</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>ekko-lightbox</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>webrtc-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-client</artifactId>
</dependency>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-utils-js</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.3.RELEASE</version>
<configuration>
<mainClass>com.bolingcavalry.playerwithrecord.PlayerWithRecorder</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
</project>
- 配置文件application.properties很简单:
# 端口
server.port=8080
#
spring.application.name=PlayerWithRecorder
- 新增一个数据结构UserSession.java,每个网页都对应一个UserSession实例,重点关注的是release方法,在停止播放时调用此方法释放播放器和WebRTC连接资源:
package com.bolingcavalry.playerwithrecord;
import org.kurento.client.IceCandidate;
import org.kurento.client.MediaPipeline;
import org.kurento.client.PlayerEndpoint;
import org.kurento.client.WebRtcEndpoint;
public class UserSession {
private WebRtcEndpoint webRtcEndpoint;
private MediaPipeline mediaPipeline;
private PlayerEndpoint playerEndpoint;
public UserSession() {
}
public WebRtcEndpoint getWebRtcEndpoint() {
return webRtcEndpoint;
}
public void setWebRtcEndpoint(WebRtcEndpoint webRtcEndpoint) {
this.webRtcEndpoint = webRtcEndpoint;
}
public MediaPipeline getMediaPipeline() {
return mediaPipeline;
}
public void setMediaPipeline(MediaPipeline mediaPipeline) {
this.mediaPipeline = mediaPipeline;
}
public void addCandidate(IceCandidate candidate) {
webRtcEndpoint.addIceCandidate(candidate);
}
public PlayerEndpoint getPlayerEndpoint() {
return playerEndpoint;
}
public void setPlayerEndpoint(PlayerEndpoint playerEndpoint) {
this.playerEndpoint = playerEndpoint;
}
public void release() {
this.playerEndpoint.stop();
this.mediaPipeline.release();
}
}
- 启动类PlayerWithRecorder.java,有两处要注意,一个是registerWebSocketHandlers方法用来绑定websocket的处理类,另一个是kurentoClient,KurentoClient.create方法的入参是KMS的服务地址:
package com.bolingcavalry.playerwithrecord;
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;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
@EnableWebSocket
@SpringBootApplication
public class PlayerWithRecorder implements WebSocketConfigurer {
@Bean
public PlayerHandler handler() {
return new PlayerHandler();
}
/**
* 实例化KurentoClient,入参是KMS地址
* @return
*/
@Bean
public KurentoClient kurentoClient() {
return KurentoClient.create("ws://192.168.91.128:8888/kurento");
}
@Bean
public ServletServerContainerFactoryBean createServletServerContainerFactoryBean() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(32768);
return container;
}
/**
* 标准的WebSocket处理类绑定
* @param registry
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(handler(), "/player");
}
public static void main(String[] args) throws Exception {
SpringApplication.run(PlayerWithRecorder.class, args);
}
}
- 接下来就是websocket的处理类PlayerHandler.java,这是本篇的核心,有几处重点稍后会提到:
package com.bolingcavalry.playerwithrecord;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.ConcurrentHashMap;
import org.kurento.client.EndOfStreamEvent;
import org.kurento.client.ErrorEvent;
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.client.MediaState;
import org.kurento.client.MediaStateChangedEvent;
import org.kurento.client.PlayerEndpoint;
import org.kurento.client.ServerManager;
import org.kurento.client.VideoInfo;
import org.kurento.client.WebRtcEndpoint;
import org.kurento.commons.exception.KurentoException;
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 PlayerHandler extends TextWebSocketHandler {
@Autowired
private KurentoClient kurento;
private final Logger log = LoggerFactory.getLogger(PlayerHandler.class);
private final Gson gson = new GsonBuilder().create();
private final ConcurrentHashMap<String, UserSession> users = new ConcurrentHashMap<>();
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
JsonObject jsonMessage = gson.fromJson(message.getPayload(), JsonObject.class);
String sessionId = session.getId();
log.debug("用户[{}]收到websocket命令: {} from sessionId", sessionId, jsonMessage);
try {
switch (jsonMessage.get("id").getAsString()) {
// 开始播放
case "start":
start(session, jsonMessage);
break;
// 停止播放
case "stop":
stop(sessionId);
break;
// 暂停
case "pause":
pause(sessionId);
break;
// 恢复
case "resume":
resume(session);
break;
// 生成监控内容
case "debugDot":
debugDot(session);
break;
// 前进或者倒退
case "doSeek":
doSeek(session, jsonMessage);
break;
// 取位置
case "getPosition":
getPosition(session);
break;
// 更新WebRTC的ICE数据
case "onIceCandidate":
onIceCandidate(sessionId, jsonMessage);
break;
default:
sendError(session, "Invalid message with id " + jsonMessage.get("id").getAsString());
break;
}
} catch (Throwable t) {
log.error("Exception handling message {} in sessionId {}", jsonMessage, sessionId, t);
sendError(session, t.getMessage());
}
}
private void start(final WebSocketSession session, JsonObject jsonMessage) {
// 1.新建MediaPipeline对象
MediaPipeline pipeline = kurento.createMediaPipeline();
// 2. 新建连接浏览器的WebRtcEndpoint对象
WebRtcEndpoint webRtcEndpoint = new WebRtcEndpoint.Builder(pipeline).build();
// 3.1 取出要播放的地址
String videourl = jsonMessage.get("videourl").getAsString以上是关于Kurento实战之五:媒体播放的主要内容,如果未能解决你的问题,请参考以下文章
自定义模块未安装在 kurento 媒体服务器上,但出现在 kurento-media-server --list
如何在 kurento 媒体服务器中进行 rtmp 流式传输?
Kurento媒体服务器抛出“处理方法时出现意外错误:未找到工厂'PlayerEndPoint''”