Implement full LiveKit API in Java
This commit is contained in:
parent
9f5e17519d
commit
e009fb598c
@ -1,12 +1,12 @@
|
||||
# Basic Java
|
||||
# OpenVidu Java
|
||||
|
||||
Basic server application built for Java with Spring Boot. It internally uses [livekit-server-sdk-kotlin](https://github.com/livekit/server-sdk-kotlin).
|
||||
OpenVidu server application built for Java with Spring Boot. It internally uses [livekit-server-sdk-kotlin](https://github.com/livekit/server-sdk-kotlin).
|
||||
|
||||
For further information, check the [tutorial documentation](https://livekit-tutorials.openvidu.io/tutorials/application-server/java/).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Java >=17](https://www.java.com/en/download/)
|
||||
- [Java >=21](https://www.java.com/en/download/)
|
||||
- [Maven](https://maven.apache.org/download.cgi)
|
||||
|
||||
## Run
|
||||
|
||||
@ -6,18 +6,18 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.4</version>
|
||||
<version>3.4.3</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>io.openvidu</groupId>
|
||||
<artifactId>basic-java</artifactId>
|
||||
<artifactId>openvidu-java</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>basic-java</name>
|
||||
<description>Basic server application built for Java with Spring Boot</description>
|
||||
<description>OpenVidu server application built for Java with Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<java.version>21</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@ -28,7 +28,7 @@
|
||||
<dependency>
|
||||
<groupId>io.livekit</groupId>
|
||||
<artifactId>livekit-server</artifactId>
|
||||
<version>0.8.2</version>
|
||||
<version>0.8.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package io.openvidu.basic.java;
|
||||
package io.openvidu.java;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class BasicJavaApplication {
|
||||
public class OpenViduJavaApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(BasicJavaApplication.class, args);
|
||||
SpringApplication.run(OpenViduJavaApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,397 @@
|
||||
package io.openvidu.java.controllers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.google.protobuf.util.JsonFormat;
|
||||
|
||||
import io.livekit.server.EgressServiceClient;
|
||||
import io.livekit.server.EncodedOutputs;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import livekit.LivekitEgress.DirectFileOutput;
|
||||
import livekit.LivekitEgress.EgressInfo;
|
||||
import livekit.LivekitEgress.EncodedFileOutput;
|
||||
import livekit.LivekitEgress.EncodedFileType;
|
||||
import livekit.LivekitEgress.StreamOutput;
|
||||
import livekit.LivekitEgress.StreamProtocol;
|
||||
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@CrossOrigin(origins = "*")
|
||||
@RestController
|
||||
@RequestMapping("/egresses")
|
||||
public class EgressController {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(EgressController.class);
|
||||
|
||||
@Value("${livekit.url}")
|
||||
private String LIVEKIT_URL;
|
||||
|
||||
@Value("${livekit.api.key}")
|
||||
private String LIVEKIT_API_KEY;
|
||||
|
||||
@Value("${livekit.api.secret}")
|
||||
private String LIVEKIT_API_SECRET;
|
||||
|
||||
private EgressServiceClient egressClient;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
egressClient = EgressServiceClient.createClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RoomComposite egress
|
||||
*
|
||||
* @param params JSON object with roomName
|
||||
* @return JSON object with the created egress
|
||||
*/
|
||||
@PostMapping("/room-composite")
|
||||
public ResponseEntity<Map<String, Object>> createRoomCompositeEgress(@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
|
||||
if (roomName == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName' is required"));
|
||||
}
|
||||
|
||||
try {
|
||||
EncodedFileOutput output = EncodedFileOutput.newBuilder()
|
||||
.setFilepath("{room_name}-{room_id}-{time}")
|
||||
.setFileType(EncodedFileType.MP4)
|
||||
.build();
|
||||
EgressInfo egress = egressClient.startRoomCompositeEgress(roomName, output, "grid")
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("egress", convertToJson(egress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error creating RoomComposite egress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RoomComposite egress to stream to a URL
|
||||
*
|
||||
* @param params JSON object with roomName and streamUrl
|
||||
* @return JSON object with the created egress
|
||||
*/
|
||||
@PostMapping("/stream")
|
||||
public ResponseEntity<Map<String, Object>> createStreamEgress(@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
String streamUrl = params.get("streamUrl");
|
||||
|
||||
if (roomName == null || streamUrl == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName' and 'streamUrl' are required"));
|
||||
}
|
||||
|
||||
try {
|
||||
StreamOutput output = StreamOutput.newBuilder()
|
||||
.setProtocol(StreamProtocol.RTMP)
|
||||
.addUrls(streamUrl)
|
||||
.build();
|
||||
EgressInfo egress = egressClient.startRoomCompositeEgress(roomName, output, "grid")
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("egress", convertToJson(egress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error creating RoomComposite egress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Participant egress
|
||||
*
|
||||
* @param params JSON object with roomName and participantIdentity
|
||||
* @return JSON object with the created egress
|
||||
*/
|
||||
@PostMapping("/participant")
|
||||
public ResponseEntity<Map<String, Object>> createParticipantEgress(@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
String participantIdentity = params.get("participantIdentity");
|
||||
|
||||
if (roomName == null || participantIdentity == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName' and 'participantIdentity' are required"));
|
||||
}
|
||||
|
||||
try {
|
||||
EncodedFileOutput output = EncodedFileOutput.newBuilder()
|
||||
.setFilepath("{room_name}-{room_id}-{publisher_identity}-{time}")
|
||||
.setFileType(EncodedFileType.MP4)
|
||||
.build();
|
||||
EncodedOutputs outputs = new EncodedOutputs(output, null, null, null);
|
||||
EgressInfo egress = egressClient.startParticipantEgress(roomName, participantIdentity, outputs, false)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("egress", convertToJson(egress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error creating Participant egress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TrackComposite egress
|
||||
*
|
||||
* @param params JSON object with roomName, videoTrackId and audioTrackId
|
||||
* @return JSON object with the created egress
|
||||
*/
|
||||
@PostMapping("/track-composite")
|
||||
public ResponseEntity<Map<String, Object>> createTrackCompositeEgress(@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
String videoTrackId = params.get("videoTrackId");
|
||||
String audioTrackId = params.get("audioTrackId");
|
||||
|
||||
if (roomName == null || videoTrackId == null || audioTrackId == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName', 'videoTrackId' and 'audioTrackId' are required"));
|
||||
}
|
||||
|
||||
try {
|
||||
EncodedFileOutput output = EncodedFileOutput.newBuilder()
|
||||
.setFilepath("{room_name}-{room_id}-{publisher_identity}-{time}")
|
||||
.setFileType(EncodedFileType.MP4)
|
||||
.build();
|
||||
EgressInfo egress = egressClient.startTrackCompositeEgress(roomName, output, audioTrackId, videoTrackId)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("egress", convertToJson(egress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error creating TrackComposite egress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Track egress
|
||||
*
|
||||
* @param params JSON object with roomName and trackId
|
||||
* @return JSON object with the created egress
|
||||
*/
|
||||
@PostMapping("/track")
|
||||
public ResponseEntity<Map<String, Object>> createTrackEgress(@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
String trackId = params.get("trackId");
|
||||
|
||||
if (roomName == null || trackId == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName' and 'trackId' are required"));
|
||||
}
|
||||
|
||||
try {
|
||||
DirectFileOutput output = DirectFileOutput.newBuilder()
|
||||
.setFilepath("{room_name}-{room_id}-{publisher_identity}-{track_source}-{track_id}-{time}")
|
||||
.build();
|
||||
EgressInfo egress = egressClient.startTrackEgress(roomName, output, trackId)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("egress", convertToJson(egress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error creating Track egress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Web egress
|
||||
*
|
||||
* @param params JSON object with url
|
||||
* @return JSON object with the created egress
|
||||
*/
|
||||
@PostMapping("/web")
|
||||
public ResponseEntity<Map<String, Object>> createWebEgress(@RequestBody Map<String, String> params) {
|
||||
String url = params.get("url");
|
||||
|
||||
if (url == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'url' is required"));
|
||||
}
|
||||
|
||||
try {
|
||||
EncodedFileOutput output = EncodedFileOutput.newBuilder()
|
||||
.setFilepath("{time}")
|
||||
.setFileType(EncodedFileType.MP4)
|
||||
.build();
|
||||
EgressInfo egress = egressClient.startWebEgress(url, output)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("egress", convertToJson(egress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error creating Web egress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List egresses
|
||||
* If an egress ID is provided, only that egress is listed
|
||||
* If a room name is provided, only egresses for that room are listed
|
||||
* If active is true, only active egresses are listed
|
||||
*
|
||||
* @param egressId Optional egress ID to filter
|
||||
* @param roomName Optional room name to filter
|
||||
* @param active Optional flag to filter active egresses
|
||||
* @return JSON object with the list of egresses
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<Map<String, Object>> listEgresses(@RequestParam(required = false) String egressId,
|
||||
@RequestParam(required = false) String roomName, @RequestParam(required = false) Boolean active) {
|
||||
try {
|
||||
List<EgressInfo> egresses = egressClient.listEgress(roomName, egressId, active)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("egresses", convertListToJson(egresses)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error listing egresses";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update egress layout
|
||||
*
|
||||
* @param params JSON object with layout
|
||||
* @return JSON object with the updated egress
|
||||
*/
|
||||
@PostMapping("/{egressId}/layout")
|
||||
public ResponseEntity<Map<String, Object>> updateEgressLayout(@PathVariable String egressId,
|
||||
@RequestBody Map<String, String> params) {
|
||||
String layout = params.get("layout");
|
||||
|
||||
if (layout == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'layout' is required"));
|
||||
}
|
||||
|
||||
try {
|
||||
EgressInfo egress = egressClient.updateLayout(egressId, layout)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("egress", convertToJson(egress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error updating egress layout";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/remove stream URLs to an egress
|
||||
*
|
||||
* @param params JSON object with streamUrlsToAdd and streamUrlsToRemove
|
||||
* @return JSON object with the updated egress
|
||||
*/
|
||||
@PostMapping("/{egressId}/streams")
|
||||
public ResponseEntity<Map<String, Object>> updateEgressStream(@PathVariable String egressId,
|
||||
@RequestBody Map<String, Object> params) {
|
||||
Object streamUrlsToAddObj = params.get("streamUrlsToAdd");
|
||||
Object streamUrlsToRemoveObj = params.get("streamUrlsToRemove");
|
||||
|
||||
if (!isStringList(streamUrlsToAddObj) || !isStringList(streamUrlsToRemoveObj)) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage",
|
||||
"'streamUrlsToAdd' and 'streamUrlsToRemove' are required and must be arrays"));
|
||||
}
|
||||
|
||||
List<String> streamUrlsToAdd = convertToStringList(streamUrlsToAddObj);
|
||||
List<String> streamUrlsToRemove = convertToStringList(streamUrlsToRemoveObj);
|
||||
|
||||
try {
|
||||
EgressInfo egress = egressClient.updateStream(egressId, streamUrlsToAdd, streamUrlsToRemove)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("egress", convertToJson(egress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error updating egress streams";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop an egress
|
||||
*
|
||||
* @return JSON object with success message
|
||||
*/
|
||||
@DeleteMapping("/{egressId}")
|
||||
public ResponseEntity<Map<String, Object>> stopEgress(@PathVariable String egressId) {
|
||||
try {
|
||||
egressClient.stopEgress(egressId)
|
||||
.execute();
|
||||
return ResponseEntity.ok(Map.of("message", "Egress stopped"));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error stopping egress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> convertToJson(EgressInfo egress)
|
||||
throws InvalidProtocolBufferException, JsonProcessingException, JsonMappingException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String rawJson = JsonFormat.printer().print(egress);
|
||||
Map<String, Object> json = objectMapper.readValue(rawJson, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
return json;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> convertListToJson(List<EgressInfo> egresses) {
|
||||
List<Map<String, Object>> jsonList = egresses.stream().map(egress -> {
|
||||
try {
|
||||
return convertToJson(egress);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error parsing egress", e);
|
||||
return null;
|
||||
}
|
||||
}).toList();
|
||||
return jsonList;
|
||||
}
|
||||
|
||||
private boolean isStringList(Object obj) {
|
||||
return obj instanceof List<?> list && list.stream().allMatch(String.class::isInstance);
|
||||
}
|
||||
|
||||
private List<String> convertToStringList(Object obj) {
|
||||
return ((List<?>) obj).stream()
|
||||
.map(String.class::cast)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,242 @@
|
||||
package io.openvidu.java.controllers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.google.protobuf.util.JsonFormat;
|
||||
|
||||
import io.livekit.server.IngressServiceClient;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import livekit.LivekitIngress.IngressInfo;
|
||||
import livekit.LivekitIngress.IngressInput;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@CrossOrigin(origins = "*")
|
||||
@RestController
|
||||
@RequestMapping("/ingresses")
|
||||
public class IngressController {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(IngressController.class);
|
||||
|
||||
@Value("${livekit.url}")
|
||||
private String LIVEKIT_URL;
|
||||
|
||||
@Value("${livekit.api.key}")
|
||||
private String LIVEKIT_API_KEY;
|
||||
|
||||
@Value("${livekit.api.secret}")
|
||||
private String LIVEKIT_API_SECRET;
|
||||
|
||||
private IngressServiceClient ingressClient;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
ingressClient = IngressServiceClient.createClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RTMP ingress
|
||||
*
|
||||
* @param params JSON object with roomName and participantIdentity
|
||||
* @return JSON object with the created ingress
|
||||
*/
|
||||
@PostMapping("/rtmp")
|
||||
public ResponseEntity<Map<String, Object>> createRTMPIngress(@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
String participantIdentity = params.get("participantIdentity");
|
||||
|
||||
if (roomName == null || participantIdentity == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName' and 'participantIdentity' are required"));
|
||||
}
|
||||
|
||||
try {
|
||||
IngressInfo ingress = ingressClient
|
||||
.createIngress("rtmp-ingress", roomName, participantIdentity, null, IngressInput.RTMP_INPUT)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("ingress", convertToJson(ingress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error creating RTMP ingress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new WHIP ingress
|
||||
*
|
||||
* @param params JSON object with roomName and participantIdentity
|
||||
* @return JSON object with the created ingress
|
||||
*/
|
||||
@PostMapping("/whip")
|
||||
public ResponseEntity<Map<String, Object>> createWHIPIngress(@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
String participantIdentity = params.get("participantIdentity");
|
||||
|
||||
if (roomName == null || participantIdentity == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName' and 'participantIdentity' are required"));
|
||||
}
|
||||
|
||||
try {
|
||||
IngressInfo ingress = ingressClient
|
||||
.createIngress("whip-ingress", roomName, participantIdentity, null, IngressInput.WHIP_INPUT)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("ingress", convertToJson(ingress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error creating WHIP ingress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new URL ingress
|
||||
*
|
||||
* @param params JSON object with roomName, participantIdentity and url
|
||||
* @return JSON object with the created ingress
|
||||
*/
|
||||
@PostMapping("/url")
|
||||
public ResponseEntity<Map<String, Object>> createURLIngress(@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
String participantIdentity = params.get("participantIdentity");
|
||||
String url = params.get("url");
|
||||
|
||||
if (roomName == null || participantIdentity == null || url == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName', 'participantIdentity' and 'url' are required"));
|
||||
}
|
||||
|
||||
try {
|
||||
IngressInfo ingress = ingressClient
|
||||
.createIngress("url-ingress", roomName, participantIdentity, null, IngressInput.URL_INPUT, null,
|
||||
null, null, null, url)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("ingress", convertToJson(ingress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error creating URL ingress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List ingresses
|
||||
* If an ingress ID is provided, only that ingress is listed
|
||||
* If a room name is provided, only ingresses for that room are listed
|
||||
*
|
||||
* @param ingressId Optional ingress ID to filter
|
||||
* @param roomName Optional room name to filter
|
||||
* @return JSON object with the list of ingresses
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<Map<String, Object>> listIngresses(@RequestParam(required = false) String ingressId,
|
||||
@RequestParam(required = false) String roomName) {
|
||||
try {
|
||||
List<IngressInfo> ingresses = ingressClient.listIngress(roomName, ingressId)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("ingresses", convertListToJson(ingresses)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error listing ingresses";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update ingress
|
||||
*
|
||||
* @param params JSON object with roomName
|
||||
* @return JSON object with the updated ingress
|
||||
*/
|
||||
@PatchMapping("/{ingressId}")
|
||||
public ResponseEntity<Map<String, Object>> updateIngress(@PathVariable String ingressId,
|
||||
@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
|
||||
if (roomName == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName' is required"));
|
||||
}
|
||||
|
||||
try {
|
||||
// Know bug: participantIdentity must be provided in order to not fail, but it is not used
|
||||
IngressInfo ingress = ingressClient.updateIngress(ingressId, "updated-ingress", roomName, "Ingress-Participant")
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("ingress", convertToJson(ingress)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error updating ingress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete ingress
|
||||
*
|
||||
* @return JSON object with success message
|
||||
*/
|
||||
@DeleteMapping("/{ingressId}")
|
||||
public ResponseEntity<Map<String, Object>> deleteIngress(@PathVariable String ingressId) {
|
||||
try {
|
||||
ingressClient.deleteIngress(ingressId)
|
||||
.execute();
|
||||
return ResponseEntity.ok(Map.of("message", "Ingress deleted"));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error deleting ingress";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> convertToJson(IngressInfo ingress)
|
||||
throws InvalidProtocolBufferException, JsonProcessingException, JsonMappingException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String rawJson = JsonFormat.printer().print(ingress);
|
||||
Map<String, Object> json = objectMapper.readValue(rawJson, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
return json;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> convertListToJson(List<IngressInfo> ingresses) {
|
||||
List<Map<String, Object>> jsonList = ingresses.stream().map(ingress -> {
|
||||
try {
|
||||
return convertToJson(ingress);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error parsing ingress", e);
|
||||
return null;
|
||||
}
|
||||
}).toList();
|
||||
return jsonList;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,399 @@
|
||||
package io.openvidu.java.controllers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.google.protobuf.MessageOrBuilder;
|
||||
import com.google.protobuf.util.JsonFormat;
|
||||
|
||||
import io.livekit.server.RoomServiceClient;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import livekit.LivekitModels.Room;
|
||||
import livekit.LivekitModels.TrackInfo;
|
||||
import livekit.LivekitModels.DataPacket;
|
||||
import livekit.LivekitModels.ParticipantInfo;
|
||||
import livekit.LivekitModels.ParticipantPermission;
|
||||
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@CrossOrigin(origins = "*")
|
||||
@RestController
|
||||
@RequestMapping("/rooms")
|
||||
public class RoomController {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RoomController.class);
|
||||
|
||||
@Value("${livekit.url}")
|
||||
private String LIVEKIT_URL;
|
||||
|
||||
@Value("${livekit.api.key}")
|
||||
private String LIVEKIT_API_KEY;
|
||||
|
||||
@Value("${livekit.api.secret}")
|
||||
private String LIVEKIT_API_SECRET;
|
||||
|
||||
private RoomServiceClient roomClient;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
roomClient = RoomServiceClient.createClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new room
|
||||
*
|
||||
* @param params JSON object with roomName
|
||||
* @return JSON object with the created room
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<Map<String, Object>> createRoom(@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
|
||||
if (roomName == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName' is required"));
|
||||
}
|
||||
|
||||
try {
|
||||
Room room = roomClient.createRoom(roomName)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("room", convertToJson(room)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error creating room";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List rooms.
|
||||
* If a room name is provided, only that room is listed
|
||||
*
|
||||
* @param roomName Optional room name to filter
|
||||
* @return JSON object with the list of rooms
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<Map<String, Object>> listRooms(@RequestParam(required = false) String roomName) {
|
||||
try {
|
||||
List<String> roomNames = roomName != null ? List.of(roomName) : null;
|
||||
List<Room> rooms = roomClient.listRooms(roomNames)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("rooms", convertListToJson(rooms)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error listing rooms";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update room metadata
|
||||
*
|
||||
* @param params JSON object with metadata
|
||||
* @return JSON object with the updated room
|
||||
*/
|
||||
@PostMapping("/{roomName}/metadata")
|
||||
public ResponseEntity<Map<String, Object>> updateRoomMetadata(@PathVariable String roomName,
|
||||
@RequestBody Map<String, String> params) {
|
||||
String metadata = params.get("metadata");
|
||||
|
||||
if (metadata == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'metadata' is required"));
|
||||
}
|
||||
|
||||
try {
|
||||
Room room = roomClient.updateRoomMetadata(roomName, metadata)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("room", convertToJson(room)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error updating room metadata";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data message to participants in a room
|
||||
*
|
||||
* @param params JSON object with data
|
||||
* @return JSON object with success message
|
||||
*/
|
||||
@PostMapping("/{roomName}/send-data")
|
||||
public ResponseEntity<Map<String, Object>> sendData(@PathVariable String roomName,
|
||||
@RequestBody Map<String, Object> params) {
|
||||
Object rawData = params.get("data");
|
||||
|
||||
if (rawData == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'data' is required"));
|
||||
}
|
||||
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
byte[] data = objectMapper.writeValueAsBytes(rawData);
|
||||
roomClient.sendData(roomName, data, DataPacket.Kind.RELIABLE, List.of(), List.of(), "chat")
|
||||
.execute();
|
||||
return ResponseEntity.ok(Map.of("message", "Data message sent"));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error sending data message";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a room
|
||||
*
|
||||
* @return JSON object with success message
|
||||
*/
|
||||
@DeleteMapping("/{roomName}")
|
||||
public ResponseEntity<Map<String, Object>> deleteRoom(@PathVariable String roomName) {
|
||||
try {
|
||||
roomClient.deleteRoom(roomName)
|
||||
.execute();
|
||||
return ResponseEntity.ok(Map.of("message", "Room deleted"));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error deleting room";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List participants in a room
|
||||
*
|
||||
* @return JSON object with the list of participants
|
||||
*/
|
||||
@GetMapping("/{roomName}/participants")
|
||||
public ResponseEntity<Map<String, Object>> listParticipants(@PathVariable String roomName) {
|
||||
try {
|
||||
List<ParticipantInfo> participants = roomClient.listParticipants(roomName)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("participants", convertListToJson(participants)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error getting participants";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a participant in a room
|
||||
*
|
||||
* @return JSON object with the participant
|
||||
*/
|
||||
@GetMapping("/{roomName}/participants/{participantIdentity}")
|
||||
public ResponseEntity<Map<String, Object>> getParticipant(@PathVariable String roomName,
|
||||
@PathVariable String participantIdentity) {
|
||||
try {
|
||||
ParticipantInfo participant = roomClient.getParticipant(roomName, participantIdentity)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("participant", convertToJson(participant)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error getting participant";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a participant in a room
|
||||
*
|
||||
* @param params JSON object with metadata (optional)
|
||||
* @return JSON object with the updated participant
|
||||
*/
|
||||
@PatchMapping("/{roomName}/participants/{participantIdentity}")
|
||||
public ResponseEntity<Map<String, Object>> updateParticipant(@PathVariable String roomName,
|
||||
@PathVariable String participantIdentity, @RequestBody Map<String, String> params) {
|
||||
String metadata = params.get("metadata");
|
||||
|
||||
try {
|
||||
ParticipantPermission permissions = ParticipantPermission.newBuilder()
|
||||
.setCanPublish(false)
|
||||
.setCanSubscribe(true)
|
||||
.build();
|
||||
ParticipantInfo participant = roomClient
|
||||
.updateParticipant(roomName, participantIdentity, null, metadata, permissions)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("participant", convertToJson(participant)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error updating participant";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a participant from a room
|
||||
*
|
||||
* @return JSON object with success message
|
||||
*/
|
||||
@DeleteMapping("/{roomName}/participants/{participantIdentity}")
|
||||
public ResponseEntity<Map<String, Object>> removeParticipant(@PathVariable String roomName,
|
||||
@PathVariable String participantIdentity) {
|
||||
try {
|
||||
roomClient.removeParticipant(roomName, participantIdentity)
|
||||
.execute();
|
||||
return ResponseEntity.ok(Map.of("message", "Participant removed"));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error removing participant";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mute published track of a participant in a room
|
||||
*
|
||||
* @param params JSON object with trackId
|
||||
* @return JSON object with updated track
|
||||
*/
|
||||
@PostMapping("/{roomName}/participants/{participantIdentity}/mute")
|
||||
public ResponseEntity<Map<String, Object>> muteParticipant(@PathVariable String roomName,
|
||||
@PathVariable String participantIdentity, @RequestBody Map<String, String> params) {
|
||||
String trackId = params.get("trackId");
|
||||
|
||||
if (trackId == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'trackId' is required"));
|
||||
}
|
||||
|
||||
try {
|
||||
TrackInfo track = roomClient.mutePublishedTrack(roomName, participantIdentity, trackId, true)
|
||||
.execute()
|
||||
.body();
|
||||
return ResponseEntity.ok(Map.of("track", convertToJson(track)));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error muting track";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe participant to tracks in a room
|
||||
*
|
||||
* @param params JSON object with list of trackIds
|
||||
* @return JSON object with success message
|
||||
*/
|
||||
@PostMapping("/{roomName}/participants/{participantIdentity}/subscribe")
|
||||
public ResponseEntity<Map<String, Object>> subscribeParticipant(@PathVariable String roomName,
|
||||
@PathVariable String participantIdentity, @RequestBody Map<String, Object> params) {
|
||||
Object trackIdsObj = params.get("trackIds");
|
||||
|
||||
if (!isStringList(trackIdsObj)) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'trackIds' is required and must be an array"));
|
||||
}
|
||||
|
||||
List<String> trackIds = convertToStringList(trackIdsObj);
|
||||
|
||||
try {
|
||||
roomClient.updateSubscriptions(roomName, participantIdentity, trackIds, true)
|
||||
.execute();
|
||||
return ResponseEntity.ok(Map.of("message", "Participant subscribed to tracks"));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error subscribing participant to tracks";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe participant from tracks in a room
|
||||
*
|
||||
* @param params JSON object with list of trackIds
|
||||
* @return JSON object with success message
|
||||
*/
|
||||
@PostMapping("/{roomName}/participants/{participantIdentity}/unsubscribe")
|
||||
public ResponseEntity<Map<String, Object>> unsubscribeParticipant(@PathVariable String roomName,
|
||||
@PathVariable String participantIdentity, @RequestBody Map<String, Object> params) {
|
||||
Object trackIdsObj = params.get("trackIds");
|
||||
|
||||
if (!isStringList(trackIdsObj)) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'trackIds' is required and must be an array"));
|
||||
}
|
||||
|
||||
List<String> trackIds = convertToStringList(trackIdsObj);
|
||||
|
||||
try {
|
||||
roomClient.updateSubscriptions(roomName, participantIdentity, trackIds, false)
|
||||
.execute();
|
||||
return ResponseEntity.ok(Map.of("message", "Participant unsubscribed from tracks"));
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Error unsubscribing participant from tracks";
|
||||
LOGGER.error(errorMessage, e);
|
||||
return ResponseEntity.internalServerError()
|
||||
.body(Map.of("errorMessage", errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends MessageOrBuilder> Map<String, Object> convertToJson(T object)
|
||||
throws InvalidProtocolBufferException, JsonProcessingException, JsonMappingException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String rawJson = JsonFormat.printer().print(object);
|
||||
Map<String, Object> json = objectMapper.readValue(rawJson, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
return json;
|
||||
}
|
||||
|
||||
private <T extends MessageOrBuilder> List<Map<String, Object>> convertListToJson(List<T> objects) {
|
||||
List<Map<String, Object>> jsonList = objects.stream().map(object -> {
|
||||
try {
|
||||
return convertToJson(object);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error parsing egress", e);
|
||||
return null;
|
||||
}
|
||||
}).toList();
|
||||
return jsonList;
|
||||
}
|
||||
|
||||
private boolean isStringList(Object obj) {
|
||||
return obj instanceof List<?> list && !list.isEmpty() && list.stream().allMatch(String.class::isInstance);
|
||||
}
|
||||
|
||||
private List<String> convertToStringList(Object obj) {
|
||||
return ((List<?>) obj).stream()
|
||||
.map(String.class::cast)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package io.openvidu.basic.java;
|
||||
package io.openvidu.java.controllers;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@ -7,18 +7,19 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.livekit.server.AccessToken;
|
||||
import io.livekit.server.CanPublish;
|
||||
import io.livekit.server.CanSubscribe;
|
||||
import io.livekit.server.RoomJoin;
|
||||
import io.livekit.server.RoomName;
|
||||
import io.livekit.server.WebhookReceiver;
|
||||
import livekit.LivekitWebhook.WebhookEvent;
|
||||
|
||||
@CrossOrigin(origins = "*")
|
||||
@RestController
|
||||
public class Controller {
|
||||
@RequestMapping("/token")
|
||||
public class TokenController {
|
||||
|
||||
@Value("${livekit.api.key}")
|
||||
private String LIVEKIT_API_KEY;
|
||||
@ -27,36 +28,30 @@ public class Controller {
|
||||
private String LIVEKIT_API_SECRET;
|
||||
|
||||
/**
|
||||
* Create a new token for a participant to join a room
|
||||
*
|
||||
* @param params JSON object with roomName and participantName
|
||||
* @return JSON object with the JWT token
|
||||
*/
|
||||
@PostMapping(value = "/token")
|
||||
@PostMapping
|
||||
public ResponseEntity<Map<String, String>> createToken(@RequestBody Map<String, String> params) {
|
||||
String roomName = params.get("roomName");
|
||||
String participantName = params.get("participantName");
|
||||
|
||||
if (roomName == null || participantName == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("errorMessage", "roomName and participantName are required"));
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("errorMessage", "'roomName' and 'participantName' are required"));
|
||||
}
|
||||
|
||||
AccessToken token = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
token.setName(participantName);
|
||||
token.setIdentity(participantName);
|
||||
token.addGrants(new RoomJoin(true), new RoomName(roomName));
|
||||
token.addGrants(
|
||||
new RoomJoin(true),
|
||||
new RoomName(roomName),
|
||||
new CanPublish(true),
|
||||
new CanSubscribe(true));
|
||||
|
||||
return ResponseEntity.ok(Map.of("token", token.toJwt()));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/livekit/webhook", consumes = "application/webhook+json")
|
||||
public ResponseEntity<String> receiveWebhook(@RequestHeader("Authorization") String authHeader, @RequestBody String body) {
|
||||
WebhookReceiver webhookReceiver = new WebhookReceiver(LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
try {
|
||||
WebhookEvent event = webhookReceiver.receive(body, authHeader);
|
||||
System.out.println("LiveKit Webhook: " + event.toString());
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error validating webhook event: " + e.getMessage());
|
||||
}
|
||||
return ResponseEntity.ok("ok");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package io.openvidu.java.controllers;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.livekit.server.WebhookReceiver;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import livekit.LivekitWebhook.WebhookEvent;
|
||||
|
||||
@CrossOrigin(origins = "*")
|
||||
@RestController
|
||||
@RequestMapping(value = "/livekit/webhook", consumes = "application/webhook+json")
|
||||
public class WebhookController {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(WebhookController.class);
|
||||
|
||||
@Value("${livekit.api.key}")
|
||||
private String LIVEKIT_API_KEY;
|
||||
|
||||
@Value("${livekit.api.secret}")
|
||||
private String LIVEKIT_API_SECRET;
|
||||
|
||||
private WebhookReceiver webhookReceiver;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
webhookReceiver = new WebhookReceiver(LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
}
|
||||
|
||||
@PostMapping()
|
||||
public ResponseEntity<String> receiveWebhook(@RequestHeader("Authorization") String authHeader,
|
||||
@RequestBody String body) {
|
||||
try {
|
||||
WebhookEvent webhookEvent = webhookReceiver.receive(body, authHeader);
|
||||
System.out.println("LiveKit Webhook: " + webhookEvent.toString());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error validating webhook event", e);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok("ok");
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
spring.application.name=basic-java
|
||||
spring.application.name=openvidu-java
|
||||
server.port=${SERVER_PORT:6080}
|
||||
server.ssl.enabled=false
|
||||
|
||||
# LiveKit configuration
|
||||
livekit.url=${LIVEKIT_URL:http://localhost:7880}
|
||||
livekit.api.key=${LIVEKIT_API_KEY:devkey}
|
||||
livekit.api.secret=${LIVEKIT_API_SECRET:secret}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package io.openvidu.basic.java;
|
||||
package io.openvidu.java;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class BasicJavaApplicationTests {
|
||||
class OpenViduJavaApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
Loading…
x
Reference in New Issue
Block a user