Java最全Kurento实战之五:媒体播放,大佬带你看源码

总结

谈到面试,其实说白了就是刷题刷题刷题,天天作死的刷。。。。。

为了准备这个“金三银四”的春招,狂刷一个月的题,狂补超多的漏洞知识,像这次美团面试问的算法、数据库、Redis、设计模式等这些题目都是我刷到过的

并且我也将自己刷的题全部整理成了PDF或者Word文档(含详细答案解析)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

66个Java面试知识点

架构专题(MySQL,Java,Redis,线程,并发,设计模式,Nginx,Linux,框架,微服务等)+大厂面试题详解(百度,阿里,腾讯,华为,迅雷,网易,中兴,北京中软等)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

算法刷题(PDF)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

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();

// 3.2 新建负责播放的PlayerEndpoint对象

final PlayerEndpoint playerEndpoint = new PlayerEndpoint.Builder(pipeline, videourl).build();

// 4 playerEndpoint连接webRtcEndpoint,这样playerEndpoint解码出的内容通过webRtcEndpoint给到浏览器

playerEndpoint.connect(webRtcEndpoint);

// 5. WebRtc相关的操作

// 5.1 一旦收到KMS的candidate就立即给到前端

webRtcEndpoint.addIceCandidateFoundListener(new EventListener() {

@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.sendMessage(new TextMessage(response.toString()));

}

} catch (IOException e) {

log.debug(e.getMessage());

}

}

});

// SDP offer是前端给的

String sdpOffer = jsonMessage.get(“sdpOffer”).getAsString();

// 给前端准备SDP answer

String sdpAnswer = webRtcEndpoint.processOffer(sdpOffer);

log.info(“[Handler::start] SDP Offer from browser to KMS:\n{}”, sdpOffer);

log.info(“[Handler::start] SDP Answer from KMS to browser:\n{}”, sdpAnswer);

JsonObject response = new JsonObject();

response.addProperty(“id”, “startResponse”);

response.addProperty(“sdpAnswer”, sdpAnswer);

sendMessage(session, response.toString());

// 6. 和媒体播放有关的操作

// 6.1 KMS会发送和媒体播放有关的消息过来,如果连接媒体成功,就把获取到的相关参数给到前端

webRtcEndpoint.addMediaStateChangedListener(new EventListener() {

@Override

public void onEvent(MediaStateChangedEvent event) {

if (event.getNewState() == MediaState.CONNECTED) {

// 媒体相关的信息可以用getVideoInfo去的

VideoInfo videoInfo = playerEndpoint.getVideoInfo();

JsonObject response = new JsonObject();

response.addProperty(“id”, “videoInfo”);

response.addProperty(“isSeekable”, videoInfo.getIsSeekable());

response.addProperty(“initSeekable”, videoInfo.getSeekableInit());

response.addProperty(“endSeekable”, videoInfo.getSeekableEnd());

response.addProperty(“videoDuration”, videoInfo.getDuration());

// 把这些媒体信息给前端

sendMessage(session, response.toString());

}

}

});

// 让KMS把它的ICD Candidate发过来(前面的监听会收到)

webRtcEndpoint.gatherCandidates();

// 7.1 添加媒体播放的监听:异常消息

playerEndpoint.addErrorListener(new EventListener() {

@Override

public void onEvent(ErrorEvent event) {

log.info(“ErrorEvent: {}”, event.getDescription());

// 通知前端停止播放

sendPlayEnd(session);

}

});

// 7.2 添加媒体播放的监听:播放结束

playerEndpoint.addEndOfStreamListener(new EventListener() {

@Override

public void onEvent(EndOfStreamEvent event) {

log.info(“EndOfStreamEvent: {}”, event.getTimestamp());

// 通知前端停止播放

sendPlayEnd(session);

}

});

// 通过KMS开始连接远程媒体

playerEndpoint.play();

// 将pipeline、webRtcEndpoint、playerEndpoint这些信息放入UserSession对象中,

// 这样方便处理前端发过来的各种命令

final UserSession user = new UserSession();

user.setMediaPipeline(pipeline);

user.setWebRtcEndpoint(webRtcEndpoint);

user.setPlayerEndpoint(playerEndpoint);

users.put(session.getId(), user);

}

/**

  • 暂停播放

  • @param sessionId

*/

private void pause(String sessionId) {

UserSession user = users.get(sessionId);

if (user != null) {

user.getPlayerEndpoint().pause();

}

}

/**

  • 从暂停恢复

  • @param session

*/

private void resume(final WebSocketSession session) {

UserSession user = users.get(session.getId());

if (user != null) {

user.getPlayerEndpoint().play();

VideoInfo videoInfo = user.getPlayerEndpoint().getVideoInfo();

JsonObject response = new JsonObject();

response.addProperty(“id”, “videoInfo”);

response.addProperty(“isSeekable”, videoInfo.getIsSeekable());

response.addProperty(“initSeekable”, videoInfo.getSeekableInit());

response.addProperty(“endSeekable”, videoInfo.getSeekableEnd());

response.addProperty(“videoDuration”, videoInfo.getDuration());

sendMessage(session, response.toString());

}

}

/**

  • 停止播放

  • @param sessionId

*/

private void stop(String sessionId) {

UserSession user = users.remove(sessionId);

if (user != null) {

user.release();

}

}

/**

  • 取得Gstreamer的dot内容,这样的内容可以被graphviz工具解析成拓扑图

  • @param session

*/

private void debugDot(final WebSocketSession session) {

UserSession user = users.get(session.getId());

if (user != null) {

final String pipelineDot = user.getMediaPipeline().getGstreamerDot();

try (PrintWriter out = new PrintWriter(“player.dot”)) {

out.println(pipelineDot);

} catch (IOException ex) {

log.error(“[Handler::debugDot] Exception: {}”, ex.getMessage());

}

final String playerDot = user.getPlayerEndpoint().getElementGstreamerDot();

try (PrintWriter out = new PrintWriter(“player-decoder.dot”)) {

out.println(playerDot);

} catch (IOException ex) {

log.error(“[Handler::debugDot] Exception: {}”, ex.getMessage());

}

}

ServerManager sm = kurento.getServerManager();

log.warn(“[Handler::debugDot] CPU COUNT: {}”, sm.getCpuCount());

log.warn(“[Handler::debugDot] CPU USAGE: {}”, sm.getUsedCpu(1000));

log.warn(“[Handler::debugDot] RAM USAGE: {}”, sm.getUsedMemory());

}

/**

  • 跳转到指定位置

  • @param session

  • @param jsonMessage

*/

private void doSeek(final WebSocketSession session, JsonObject jsonMessage) {

UserSession user = users.get(session.getId());

if (user != null) {

try {

user.getPlayerEndpoint().setPosition(jsonMessage.get(“position”).getAsLong());

} catch (KurentoException e) {

log.debug(“The seek cannot be performed”);

JsonObject response = new JsonObject();

response.addProperty(“id”, “seek”);

response.addProperty(“message”, “Seek failed”);

sendMessage(session, response.toString());

}

}

}

/**

  • 取得当前播放位置

  • @param session

*/

private void getPosition(final WebSocketSession session) {

UserSession user = users.get(session.getId());

if (user != null) {

long position = user.getPlayerEndpoint().getPosition();

JsonObject response = new JsonObject();

response.addProperty(“id”, “position”);

response.addProperty(“position”, position);

sendMessage(session, response.toString());

}

}

/**

  • 收到前端的Ice candidate后,立即发给KMS

  • @param sessionId

  • @param jsonMessage

*/

private void onIceCandidate(String sessionId, JsonObject jsonMessage) {

UserSession user = users.get(sessionId);

if (user != null) {

JsonObject jsonCandidate = jsonMessage.get(“candidate”).getAsJsonObject();

IceCandidate candidate =

new IceCandidate(jsonCandidate.get(“candidate”).getAsString(), jsonCandidate

.get(“sdpMid”).getAsString(), jsonCandidate.get(“sdpMLineIndex”).getAsInt());

user.getWebRtcEndpoint().addIceCandidate(candidate);

}

}

/**

  • 通知前端停止播放

  • @param session

*/

public void sendPlayEnd(WebSocketSession session) {

if (users.containsKey(session.getId())) {

JsonObject response = new JsonObject();

response.addProperty(“id”, “playEnd”);

sendMessage(session, response.toString());

}

}

/**

  • 将错误信息发给前端

  • @param session

  • @param message

*/

private void sendError(WebSocketSession session, String message) {

if (users.containsKey(session.getId())) {

JsonObject response = new JsonObject();

response.addProperty(“id”, “error”);

response.addProperty(“message”, message);

sendMessage(session, response.toString());

}

}

/**

  • 给前端发送消息的方法用synchronized修饰,

  • 因为收到KMS通知的时机不确定,此时可能正在给前端发送消息,存在同时调用sendMessage的可能

最后

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
在这里插入图片描述

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

ession, response.toString());

}

}

/**

  • 将错误信息发给前端

  • @param session

  • @param message

*/

private void sendError(WebSocketSession session, String message) {

if (users.containsKey(session.getId())) {

JsonObject response = new JsonObject();

response.addProperty(“id”, “error”);

response.addProperty(“message”, message);

sendMessage(session, response.toString());

}

}

/**

  • 给前端发送消息的方法用synchronized修饰,

  • 因为收到KMS通知的时机不确定,此时可能正在给前端发送消息,存在同时调用sendMessage的可能

最后

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
[外链图片转存中…(img-JsXXuyDp-1715319099696)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值