Kurento实战之五:媒体播放

Posted 程序员欣宸

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kurento实战之五:媒体播放相关的知识,希望对你有一定的参考价值。

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本文是《Kurento实战》的第五篇,咱们用KMS的现有能力开发一个简单的媒体播放器,整体架构如下图:
    在这里插入图片描述
  • 从上图可见,实战主要内容是开发player-with-record应用,整个过程如下:
  1. 部署KMS
  2. 开发名为player-with-record的springboot应用,含简单的网页
  3. 浏览器打开网页,与player-with-record建立websocket连接,将流媒体地址发送到player-with-record
  4. player-with-record通过kurento SDK向KMS发指令,创建媒体播放和webrtc组件实例
  5. player-with-record还负责浏览器和前端页面之间的WebRTC信令传输
  6. 浏览器和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 媒体服务器的流量

Kurento媒体服务器抛出“处理方法时出现意外错误:未找到工厂'PlayerEndPoint''”

使用Kurento搭建一个流媒体服务器,实现在网页查看Rtsp流

事件未到达 kurento java 服务器