Kurento Media Server is an open-source WebRTC media server that enables building advanced video applications with capabilities like recording, mixing, computer vision, and augmented reality. When combined with Java's robustness and scalability, it becomes a powerful platform for creating enterprise-grade real-time communication systems. This article explores how to integrate Kurento Media Server with Java to build sophisticated media processing applications.
What is Kurento Media Server?
Kurento is a media server that provides capabilities for:
- WebRTC media communication
- Recording and streaming media
- Computer Vision and augmented reality
- Media Mixing (video composition)
- Transcoding between different codecs
- Real-time processing with media pipelines
Key Components:
- Kurento Media Server (KMS): The core media processing engine
- Kurento Client: Java library to control KMS
- Media Elements: Processing units (filters, mixers, recorders)
- Media Pipelines: Chains of media elements
Architecture Overview
Java Application ↓ (JSON-RPC over WebSocket) Kurento Media Server ↓ (WebRTC/SRTP) Web Browser/Client
Media Pipeline Concept:
WebRtcEndpoint → Filter → RecorderEndpoint ↓ CompositeEndpoint
Setting Up Dependencies
Maven Dependencies:
<properties>
<kurento.version>7.0.0</kurento.version>
<spring.boot.version>2.7.0</spring.boot.version>
</properties>
<dependencies>
<!-- Kurento Client -->
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-client</artifactId>
<version>${kurento.version}</version>
</dependency>
<!-- Spring Boot WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
Gradle:
dependencies {
implementation("org.kurento:kurento-client:7.0.0")
implementation("org.springframework.boot:spring-boot-starter-websocket:2.7.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3")
}
Basic Kurento Setup and Configuration
Spring Boot Configuration:
import org.kurento.client.KurentoClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class KurentoConfig implements WebSocketConfigurer {
@Value("${kurento.ws.path:/kurento}")
private String kurentoPath;
@Value("${kurento.server.url:ws://localhost:8888/kurento}")
private String kurentoServerUrl;
@Bean
public KurentoClient kurentoClient() {
return KurentoClient.create(kurentoServerUrl);
}
@Bean
public WebRtcHandler webRtcHandler() {
return new WebRtcHandler();
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webRtcHandler(), kurentoPath)
.setAllowedOrigins("*");
}
}
Example 1: One-to-One Video Call
WebRTC Handler for Video Call:
import org.kurento.client.*;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class WebRtcHandler extends TextWebSocketHandler {
private final ConcurrentMap<String, UserSession> users = new ConcurrentHashMap<>();
private final ObjectMapper mapper = new ObjectMapper();
private final KurentoClient kurento;
public WebRtcHandler(KurentoClient kurento) {
this.kurento = kurento;
}
@Override
public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception {
UserSession userSession = new UserSession(wsSession);
users.put(wsSession.getId(), userSession);
// Send connection confirmation
sendMessage(wsSession, new JsonMessage("id", wsSession.getId()));
}
@Override
protected void handleTextMessage(WebSocketSession wsSession, TextMessage message) throws Exception {
try {
JsonMessage jsonMessage = mapper.readValue(message.getPayload(), JsonMessage.class);
switch (jsonMessage.getId()) {
case "start":
startCommunication(wsSession, jsonMessage);
break;
case "stop":
stopCommunication(wsSession);
break;
case "onIceCandidate":
onIceCandidate(wsSession, jsonMessage);
break;
default:
sendError(wsSession, "Invalid message with id " + jsonMessage.getId());
}
} catch (IOException e) {
sendError(wsSession, e.getMessage());
}
}
private void startCommunication(WebSocketSession wsSession, JsonMessage message) {
UserSession userSession = users.get(wsSession.getId());
try {
// Create media pipeline
MediaPipeline pipeline = kurento.createMediaPipeline();
userSession.setMediaPipeline(pipeline);
// Create WebRTC endpoint
WebRtcEndpoint webRtcEndpoint = new WebRtcEndpoint.Builder(pipeline).build();
userSession.setWebRtcEndpoint(webRtcEndpoint);
// ICE candidate handling
webRtcEndpoint.addIceCandidateFoundListener(event -> {
try {
JsonMessage candidateMsg = new JsonMessage("iceCandidate",
event.getCandidate().getCandidate(),
event.getCandidate().getSdpMid(),
event.getCandidate().getSdpMLineIndex());
sendMessage(wsSession, candidateMsg);
} catch (IOException e) {
e.printStackTrace();
}
});
// Generate SDP offer
String sdpOffer = message.getSdpOffer();
String sdpAnswer = webRtcEndpoint.processOffer(sdpOffer);
// Start gathering ICE candidates
webRtcEndpoint.gatherCandidates();
// Send SDP answer back to client
JsonMessage response = new JsonMessage("startResponse", sdpAnswer);
sendMessage(wsSession, response);
} catch (Throwable t) {
sendError(wsSession, t.getMessage());
cleanup(userSession);
}
}
private void onIceCandidate(WebSocketSession wsSession, JsonMessage message) {
UserSession userSession = users.get(wsSession.getId());
if (userSession != null && userSession.getWebRtcEndpoint() != null) {
IceCandidate candidate = new IceCandidate(
message.getCandidate(),
message.getSdpMid(),
message.getSdpMLineIndex()
);
userSession.getWebRtcEndpoint().addIceCandidate(candidate);
}
}
private void stopCommunication(WebSocketSession wsSession) {
UserSession userSession = users.remove(wsSession.getId());
if (userSession != null) {
cleanup(userSession);
}
}
private void cleanup(UserSession userSession) {
if (userSession != null) {
if (userSession.getWebRtcEndpoint() != null) {
userSession.getWebRtcEndpoint().release();
}
if (userSession.getMediaPipeline() != null) {
userSession.getMediaPipeline().release();
}
}
}
private void sendMessage(WebSocketSession session, JsonMessage message) throws IOException {
String json = mapper.writeValueAsString(message);
session.sendMessage(new TextMessage(json));
}
private void sendError(WebSocketSession session, String message) {
try {
sendMessage(session, new JsonMessage("error", message));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
stopCommunication(session);
}
// UserSession inner class
private static class UserSession {
private final WebSocketSession webSocketSession;
private MediaPipeline mediaPipeline;
private WebRtcEndpoint webRtcEndpoint;
public UserSession(WebSocketSession webSocketSession) {
this.webSocketSession = webSocketSession;
}
// Getters and setters
public WebSocketSession getWebSocketSession() { return webSocketSession; }
public MediaPipeline getMediaPipeline() { return mediaPipeline; }
public void setMediaPipeline(MediaPipeline mediaPipeline) { this.mediaPipeline = mediaPipeline; }
public WebRtcEndpoint getWebRtcEndpoint() { return webRtcEndpoint; }
public void setWebRtcEndpoint(WebRtcEndpoint webRtcEndpoint) { this.webRtcEndpoint = webRtcEndpoint; }
}
// JSON Message wrapper
public static class JsonMessage {
private String id;
private String sdpOffer;
private String sdpAnswer;
private String candidate;
private String sdpMid;
private Integer sdpMLineIndex;
private String message;
public JsonMessage() {}
public JsonMessage(String id, String message) {
this.id = id;
this.message = message;
}
public JsonMessage(String id, String candidate, String sdpMid, Integer sdpMLineIndex) {
this.id = id;
this.candidate = candidate;
this.sdpMid = sdpMid;
this.sdpMLineIndex = sdpMLineIndex;
}
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getSdpOffer() { return sdpOffer; }
public void setSdpOffer(String sdpOffer) { this.sdpOffer = sdpOffer; }
public String getSdpAnswer() { return sdpAnswer; }
public void setSdpAnswer(String sdpAnswer) { this.sdpAnswer = sdpAnswer; }
public String getCandidate() { return candidate; }
public void setCandidate(String candidate) { this.candidate = candidate; }
public String getSdpMid() { return sdpMid; }
public void setSdpMid(String sdpMid) { this.sdpMid = sdpMid; }
public Integer getSdpMLineIndex() { return sdpMLineIndex; }
public void setSdpMLineIndex(Integer sdpMLineIndex) { this.sdpMLineIndex = sdpMLineIndex; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
}
Example 2: Group Video Call with Composite
Group Video Call Handler:
import org.kurento.client.*;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class GroupCallHandler extends TextWebSocketHandler {
private final ConcurrentMap<String, UserSession> users = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Room> rooms = new ConcurrentHashMap<>();
private final ObjectMapper mapper = new ObjectMapper();
private final KurentoClient kurento;
public GroupCallHandler(KurentoClient kurento) {
this.kurento = kurento;
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
JsonMessage jsonMessage = mapper.readValue(message.getPayload(), JsonMessage.class);
switch (jsonMessage.getId()) {
case "joinRoom":
joinRoom(session, jsonMessage);
break;
case "receiveVideoFrom":
receiveVideoFrom(session, jsonMessage);
break;
case "leaveRoom":
leaveRoom(session);
break;
case "onIceCandidate":
onIceCandidate(session, jsonMessage);
break;
}
}
private synchronized void joinRoom(WebSocketSession session, JsonMessage message) throws IOException {
String roomName = message.getRoomName();
String userName = message.getUserName();
Room room = rooms.computeIfAbsent(roomName, k -> new Room(roomName, kurento.createMediaPipeline()));
UserSession userSession = new UserSession(session, userName, roomName);
// Create WebRTC endpoint for this user
WebRtcEndpoint outgoingMedia = new WebRtcEndpoint.Builder(room.getPipeline()).build();
userSession.setOutgoingWebRtc(outgoingMedia);
// Configure ICE candidates
outgoingMedia.addIceCandidateFoundListener(event -> {
try {
sendMessage(session, new JsonMessage("iceCandidate",
event.getCandidate().getCandidate(),
event.getCandidate().getSdpMid(),
event.getCandidate().getSdpMLineIndex()));
} catch (IOException e) {
e.printStackTrace();
}
});
// Add user to room
room.addUser(userName, userSession);
users.put(session.getId(), userSession);
// Process SDP offer
String sdpOffer = message.getSdpOffer();
String sdpAnswer = outgoingMedia.processOffer(sdpOffer);
outgoingMedia.gatherCandidates();
// Send existing participants to new user
for (String existingUserName : room.getUsers().keySet()) {
if (!existingUserName.equals(userName)) {
sendMessage(session, new JsonMessage("existingParticipant", existingUserName));
}
}
// Notify others about new participant
for (UserSession existingUser : room.getUsers().values()) {
if (!existingUser.getName().equals(userName)) {
sendMessage(existingUser.getSession(), new JsonMessage("newParticipant", userName));
}
}
// Send SDP answer
sendMessage(session, new JsonMessage("receiveVideoAnswer", sdpAnswer));
}
private synchronized void receiveVideoFrom(WebSocketSession session, JsonMessage message) throws IOException {
UserSession userSession = users.get(session.getId());
String senderName = message.getSender();
Room room = rooms.get(userSession.getRoomName());
UserSession sender = room.getUsers().get(senderName);
// Create WebRTC endpoint for receiving
WebRtcEndpoint incomingMedia = new WebRtcEndpoint.Builder(room.getPipeline()).build();
userSession.addIncomingWebRtc(senderName, incomingMedia);
// Configure ICE for incoming endpoint
incomingMedia.addIceCandidateFoundListener(event -> {
try {
sendMessage(session, new JsonMessage("iceCandidate",
event.getCandidate().getCandidate(),
event.getCandidate().getSdpMid(),
event.getCandidate().getSdpMLineIndex()));
} catch (IOException e) {
e.printStackTrace();
}
});
// Connect sender's outgoing to this user's incoming
sender.getOutgoingWebRtc().connect(incomingMedia);
// Process SDP offer
String sdpOffer = message.getSdpOffer();
String sdpAnswer = incomingMedia.processOffer(sdpOffer);
incomingMedia.gatherCandidates();
sendMessage(session, new JsonMessage("receiveVideoAnswer", sdpAnswer, senderName));
}
private synchronized void leaveRoom(WebSocketSession session) throws IOException {
UserSession userSession = users.remove(session.getId());
if (userSession != null) {
Room room = rooms.get(userSession.getRoomName());
room.removeUser(userSession.getName());
// Notify other participants
for (UserSession otherUser : room.getUsers().values()) {
sendMessage(otherUser.getSession(), new JsonMessage("participantLeft", userSession.getName()));
}
// Cleanup if room is empty
if (room.isEmpty()) {
room.getPipeline().release();
rooms.remove(room.getName());
}
userSession.close();
}
}
private void onIceCandidate(WebSocketSession session, JsonMessage message) {
UserSession userSession = users.get(session.getId());
if (userSession != null) {
IceCandidate candidate = new IceCandidate(
message.getCandidate(),
message.getSdpMid(),
message.getSdpMLineIndex()
);
// Add candidate to appropriate endpoint
String senderName = message.getSender();
if (senderName != null) {
// For incoming media
WebRtcEndpoint incoming = userSession.getIncomingWebRtc(senderName);
if (incoming != null) {
incoming.addIceCandidate(candidate);
}
} else {
// For outgoing media
WebRtcEndpoint outgoing = userSession.getOutgoingWebRtc();
if (outgoing != null) {
outgoing.addIceCandidate(candidate);
}
}
}
}
private void sendMessage(WebSocketSession session, JsonMessage message) throws IOException {
String json = mapper.writeValueAsString(message);
session.sendMessage(new TextMessage(json));
}
// Room management class
private static class Room {
private final String name;
private final MediaPipeline pipeline;
private final ConcurrentMap<String, UserSession> users = new ConcurrentHashMap<>();
public Room(String name, MediaPipeline pipeline) {
this.name = name;
this.pipeline = pipeline;
}
public String getName() { return name; }
public MediaPipeline getPipeline() { return pipeline; }
public ConcurrentMap<String, UserSession> getUsers() { return users; }
public void addUser(String userName, UserSession user) {
users.put(userName, user);
}
public void removeUser(String userName) {
users.remove(userName);
}
public boolean isEmpty() {
return users.isEmpty();
}
}
// Enhanced UserSession for group calls
private static class UserSession {
private final WebSocketSession session;
private final String name;
private final String roomName;
private WebRtcEndpoint outgoingWebRtc;
private final ConcurrentMap<String, WebRtcEndpoint> incomingWebRtc = new ConcurrentHashMap<>();
public UserSession(WebSocketSession session, String name, String roomName) {
this.session = session;
this.name = name;
this.roomName = roomName;
}
public void close() {
if (outgoingWebRtc != null) {
outgoingWebRtc.release();
}
incomingWebRtc.values().forEach(WebRtcEndpoint::release);
incomingWebRtc.clear();
}
// Getters and setters
public WebSocketSession getSession() { return session; }
public String getName() { return name; }
public String getRoomName() { return roomName; }
public WebRtcEndpoint getOutgoingWebRtc() { return outgoingWebRtc; }
public void setOutgoingWebRtc(WebRtcEndpoint outgoingWebRtc) { this.outgoingWebRtc = outgoingWebRtc; }
public void addIncomingWebRtc(String sender, WebRtcEndpoint endpoint) { incomingWebRtc.put(sender, endpoint); }
public WebRtcEndpoint getIncomingWebRtc(String sender) { return incomingWebRtc.get(sender); }
}
}
Example 3: Video Recording with Filters
Video Recording with Computer Vision:
import org.kurento.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class RecordingService {
private static final Logger log = LoggerFactory.getLogger(RecordingService.class);
private final KurentoClient kurento;
private final ConcurrentMap<String, RecordingSession> recordings = new ConcurrentHashMap<>();
public RecordingService(KurentoClient kurento) {
this.kurento = kurento;
}
public String startRecording(String sessionId, String filePath) {
try {
MediaPipeline pipeline = kurento.createMediaPipeline();
WebRtcEndpoint webRtcEndpoint = new WebRtcEndpoint.Builder(pipeline).build();
// Create recorder endpoint
RecorderEndpoint recorder = new RecorderEndpoint.Builder(pipeline, filePath)
.withMediaProfile(MediaProfileSpecType.WEBM)
.build();
// Add computer vision filter (face overlay example)
FaceOverlayFilter faceFilter = new FaceOverlayFilter.Builder(pipeline).build();
faceFilter.setOverlayedImage("https://example.com/overlay.png", -0.35f, -1.2f, 1.6f, 1.6f);
// Build pipeline: WebRTC → Face Filter → Recorder
webRtcEndpoint.connect(faceFilter);
faceFilter.connect(recorder);
// Also connect WebRTC to itself for monitoring
webRtcEndpoint.connect(webRtcEndpoint);
// Start recording
recorder.record();
RecordingSession recordingSession = new RecordingSession(
sessionId, pipeline, webRtcEndpoint, recorder, faceFilter);
recordings.put(sessionId, recordingSession);
log.info("Started recording for session {} to file: {}", sessionId, filePath);
return sessionId;
} catch (Exception e) {
log.error("Failed to start recording for session: " + sessionId, e);
throw new RuntimeException("Recording failed to start", e);
}
}
public void stopRecording(String sessionId) {
RecordingSession recording = recordings.remove(sessionId);
if (recording != null) {
try {
recording.getRecorder().stop();
recording.getPipeline().release();
log.info("Stopped recording for session: {}", sessionId);
} catch (Exception e) {
log.error("Error stopping recording for session: " + sessionId, e);
}
}
}
public WebRtcEndpoint getWebRtcEndpoint(String sessionId) {
RecordingSession recording = recordings.get(sessionId);
return recording != null ? recording.getWebRtcEndpoint() : null;
}
private static class RecordingSession {
private final String sessionId;
private final MediaPipeline pipeline;
private final WebRtcEndpoint webRtcEndpoint;
private final RecorderEndpoint recorder;
private final FaceOverlayFilter faceFilter;
public RecordingSession(String sessionId, MediaPipeline pipeline,
WebRtcEndpoint webRtcEndpoint, RecorderEndpoint recorder,
FaceOverlayFilter faceFilter) {
this.sessionId = sessionId;
this.pipeline = pipeline;
this.webRtcEndpoint = webRtcEndpoint;
this.recorder = recorder;
this.faceFilter = faceFilter;
}
// Getters
public String getSessionId() { return sessionId; }
public MediaPipeline getPipeline() { return pipeline; }
public WebRtcEndpoint getWebRtcEndpoint() { return webRtcEndpoint; }
public RecorderEndpoint getRecorder() { return recorder; }
public FaceOverlayFilter getFaceFilter() { return faceFilter; }
}
}
Example 4: Real-time Computer Vision Pipeline
Computer Vision with OpenCV Filter:
import org.kurento.client.*;
import org.kurento.module.opencvplugins.*;
public class ComputerVisionService {
private final KurentoClient kurento;
public ComputerVisionService(KurentoClient kurento) {
this.kurento = kurento;
}
public ComputerVisionSession startFaceDetection(String sessionId) {
try {
MediaPipeline pipeline = kurento.createMediaPipeline();
WebRtcEndpoint webRtcEndpoint = new WebRtcEndpoint.Builder(pipeline).build();
// Create face detection filter
FaceDetectorFilter faceDetector = new FaceDetectorFilter.Builder(pipeline).build();
faceDetector.setSendOptimalTargetFrame(false);
faceDetector.setMultiScaleFactor(1.2);
faceDetector.setScaleFactor(1.1);
// Create overlay to show detection results
FaceOverlayFilter faceOverlay = new FaceOverlayFilter.Builder(pipeline).build();
// Build pipeline: WebRTC → Face Detection → Face Overlay → WebRTC
webRtcEndpoint.connect(faceDetector);
faceDetector.connect(faceOverlay);
faceOverlay.connect(webRtcEndpoint);
// Add event listener for face detection
faceDetector.addOnFaceDetectedListener(event -> {
System.out.printf("Face detected at (%d, %d) width: %d, height: %d%n",
event.getX(), event.getY(), event.getWidth(), event.getHeight());
});
ComputerVisionSession cvSession = new ComputerVisionSession(
sessionId, pipeline, webRtcEndpoint, faceDetector, faceOverlay);
return cvSession;
} catch (Exception e) {
throw new RuntimeException("Failed to start computer vision pipeline", e);
}
}
public static class ComputerVisionSession {
private final String sessionId;
private final MediaPipeline pipeline;
private final WebRtcEndpoint webRtcEndpoint;
private final FaceDetectorFilter faceDetector;
private final FaceOverlayFilter faceOverlay;
public ComputerVisionSession(String sessionId, MediaPipeline pipeline,
WebRtcEndpoint webRtcEndpoint,
FaceDetectorFilter faceDetector,
FaceOverlayFilter faceOverlay) {
this.sessionId = sessionId;
this.pipeline = pipeline;
this.webRtcEndpoint = webRtcEndpoint;
this.faceDetector = faceDetector;
this.faceOverlay = faceOverlay;
}
public void release() {
pipeline.release();
}
// Getters
public String getSessionId() { return sessionId; }
public MediaPipeline getPipeline() { return pipeline; }
public WebRtcEndpoint getWebRtcEndpoint() { return webRtcEndpoint; }
public FaceDetectorFilter getFaceDetector() { return faceDetector; }
public FaceOverlayFilter getFaceOverlay() { return faceOverlay; }
}
}
Spring Boot Application Main Class
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.kurento.client.KurentoClient;
@SpringBootApplication
public class KurentoApplication {
@Bean
public KurentoClient kurentoClient() {
return KurentoClient.create("ws://localhost:8888/kurento");
}
@Bean
public WebRtcHandler webRtcHandler() {
return new WebRtcHandler(kurentoClient());
}
@Bean
public GroupCallHandler groupCallHandler() {
return new GroupCallHandler(kurentoClient());
}
@Bean
public RecordingService recordingService() {
return new RecordingService(kurentoClient());
}
@Bean
public ComputerVisionService computerVisionService() {
return new ComputerVisionService(kurentoClient());
}
public static void main(String[] args) {
SpringApplication.run(KurentoApplication.class, args);
}
}
Client-Side JavaScript Example
Basic WebRTC Client:
<!DOCTYPE html>
<html>
<head>
<title>Kurento Java Client</title>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
</head>
<body>
<video id="localVideo" autoplay muted></video>
<video id="remoteVideo" autoplay></video>
<button onclick="start()">Start Call</button>
<button onclick="stop()">Stop Call</button>
<script>
let localStream;
let remoteStream;
let pc;
const ws = new WebSocket('ws://localhost:8080/kurento');
ws.onmessage = function(message) {
const data = JSON.parse(message.data);
switch(data.id) {
case 'startResponse':
handleStartResponse(data);
break;
case 'iceCandidate':
handleIceCandidate(data);
break;
case 'error':
console.error('Error:', data.message);
break;
}
};
async function start() {
try {
localStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
document.getElementById('localVideo').srcObject = localStream;
pc = new RTCPeerConnection({
iceServers: [{urls: 'stun:stun.l.google.com:19302'}]
});
pc.onicecandidate = event => {
if (event.candidate) {
ws.send(JSON.stringify({
id: 'onIceCandidate',
candidate: event.candidate.candidate,
sdpMid: event.candidate.sdpMid,
sdpMLineIndex: event.candidate.sdpMLineIndex
}));
}
};
pc.ontrack = event => {
remoteStream = event.streams[0];
document.getElementById('remoteVideo').srcObject = remoteStream;
};
localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
ws.send(JSON.stringify({
id: 'start',
sdpOffer: offer.sdp
}));
} catch (error) {
console.error('Error starting call:', error);
}
}
function handleStartResponse(data) {
pc.setRemoteDescription(new RTCSessionDescription({
type: 'answer',
sdp: data.sdpAnswer
}));
}
function handleIceCandidate(data) {
pc.addIceCandidate(new RTCIceCandidate({
candidate: data.candidate,
sdpMid: data.sdpMid,
sdpMLineIndex: data.sdpMLineIndex
}));
}
function stop() {
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
}
if (pc) {
pc.close();
}
ws.send(JSON.stringify({id: 'stop'}));
}
</script>
</body>
</html>
Performance Optimization and Best Practices
1. Connection Pooling:
@Component
public class KurentoConnectionPool {
private final List<KurentoClient> clients = new ArrayList<>();
private final AtomicInteger counter = new AtomicInteger(0);
@PostConstruct
public void init() {
for (int i = 0; i < 5; i++) {
clients.add(KurentoClient.create("ws://localhost:8888/kurento"));
}
}
public KurentoClient getClient() {
int index = counter.getAndIncrement() % clients.size();
return clients.get(index);
}
}
2. Error Handling and Recovery:
public class KurentoErrorHandler {
public static void handleMediaElementError(MediaElement element, String sessionId) {
element.addErrorListener(event -> {
log.error("Media element error for session {}: {}", sessionId, event.getDescription());
// Implement recovery logic
});
}
public static void handlePipelineError(MediaPipeline pipeline, String sessionId) {
pipeline.addErrorListener(event -> {
log.error("Pipeline error for session {}: {}", sessionId, event.getDescription());
// Release and recreate pipeline
pipeline.release();
});
}
}
3. Monitoring and Metrics:
@Component
public class KurentoMetrics {
private final Meter sessionsMeter;
private final Counter errorsCounter;
private final Timer pipelineCreationTimer;
public KurentoMetrics(MeterRegistry registry) {
this.sessionsMeter = registry.meter("kurento.sessions");
this.errorsCounter = registry.counter("kurento.errors");
this.pipelineCreationTimer = registry.timer("kurento.pipeline.creation");
}
public void recordSessionStart() {
sessionsMeter.mark();
}
public void recordError() {
errorsCounter.increment();
}
public Timer.Sample startPipelineCreationTimer() {
return Timer.start(Clock.SYSTEM);
}
public void stopPipelineCreationTimer(Timer.Sample sample) {
sample.stop(pipelineCreationTimer);
}
}
Conclusion
Kurento Media Server with Java provides a powerful platform for building real-time media applications:
Key Advantages:
- Enterprise Ready: Robust Java integration for production systems
- WebRTC Native: Built-in support for modern real-time communication
- Extensible: Custom media elements and computer vision capabilities
- Scalable: Distributed deployment options
- Comprehensive: Recording, mixing, transcoding, and AI processing
Use Cases:
- Video conferencing and collaboration tools
- Live streaming platforms
- Security and surveillance systems
- Telemedicine applications
- Interactive broadcasting
- Augmented reality experiences
Best Practices:
- Resource Management: Always release media pipelines and elements
- Error Handling: Implement robust error recovery mechanisms
- Monitoring: Track performance metrics and system health
- Scalability: Use connection pooling for high-concurrency scenarios
- Security: Implement authentication and authorization for media sessions
By leveraging Kurento's powerful media processing capabilities with Java's enterprise strengths, you can build sophisticated, scalable real-time communication systems that meet modern application requirements.