Compare commits
10 Commits
master
...
livekit-ap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ee52442f6 | ||
|
|
fe7468a08c | ||
|
|
6c45efb6d8 | ||
|
|
11d0de279c | ||
|
|
820a842003 | ||
|
|
38356c3a65 | ||
|
|
e009fb598c | ||
|
|
9f5e17519d | ||
|
|
2780be22e5 | ||
|
|
e9409a2708 |
549
application-server/README.md
Normal file
549
application-server/README.md
Normal file
@ -0,0 +1,549 @@
|
||||
# Application Server Tutorials
|
||||
|
||||
These tutorials implement an application server that defines a full REST API for interacting with LiveKit in different languages using its corresponding SDKs. This is useful to see an example of how to call all differents endpoints of LiveKit Twirp API.
|
||||
|
||||
> [!NOTE]
|
||||
> By the moment, tutorials are only fully implemented in Node.js, Java and Go. Besides, AgentDispatchClient and SipClient methods are not implemented in any tutorial.
|
||||
|
||||
## Testing API endpoints
|
||||
|
||||
To test API endpoints, we have defined a [Postman collection](./application-server-tutorials.postman_collection.json) that you can import into your Postman client.
|
||||
|
||||
This collection defines a set of variables that requests use in order to reduce the amount of changes you need to make to test the API. This variables are:
|
||||
|
||||
- `BASE_URL`: The base URL of the application server. By default, it is set to `http://localhost:6080`.
|
||||
- `ROOM_URL`: The base URL of all room-related endpoints. By default, it is set to `{{BASE_URL}}/rooms`.
|
||||
- `EGRESS_URL`: The base URL of all egress-related endpoints. By default, it is set to `{{BASE_URL}}/egresses`.
|
||||
- `INGRESS_URL`: The base URL of all ingress-related endpoints. By default, it is set to `{{BASE_URL}}/ingresses`.
|
||||
- `ROOM_NAME`: The name of the room. By default, it is set to `Test Room`.
|
||||
- `PARTICIPANT_IDENTITY`: The identity of the participant. By default, it is set to `Participant1`.
|
||||
- `TRACK_ID`: The ID of the track.
|
||||
- `EGRESS_ID`: The ID of the egress.
|
||||
- `INGRESS_ID`: The ID of the ingress.
|
||||
|
||||
The collection is divided into folders, each one containing requests for a specific endpoint:
|
||||
|
||||
- `Room`: Contains requests for room-related endpoints.
|
||||
- `Egress`: Contains requests for egress-related endpoints.
|
||||
- `Ingress`: Contains requests for ingress-related endpoints.
|
||||
|
||||
In addition, there is another request apart from the folders:
|
||||
|
||||
- **Create token**: Generates a token for a participant to join a room.
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{BASE_URL}}/token`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}",
|
||||
"participantIdentity": "{{PARTICIPANT_IDENTITY}}"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"token": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### Room requests
|
||||
|
||||
The `Room` folder contains the following requests:
|
||||
|
||||
- **Create room**: Creates a new room.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{ROOM_URL}}`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"room": {...}
|
||||
}
|
||||
```
|
||||
|
||||
- **List rooms**: Retrieves a list of active rooms, optionally filtered by name.
|
||||
|
||||
- **Method**: `GET`
|
||||
- **URL**: `{{ROOM_URL}}?roomName={{ROOM_NAME}}`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"rooms": [...]
|
||||
}
|
||||
```
|
||||
|
||||
- **Update room metadata**: Updates the metadata of a room.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{ROOM_URL}}/{{ROOM_NAME}}/metadata`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"metadata": "Some data"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"room": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, a room must be created and the `ROOM_NAME` variable must be set to the name of the room.
|
||||
|
||||
- **Send data**: Sends a data message to all participants in a room.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{ROOM_URL}}/{{ROOM_NAME}}/send-data`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"some": "data"
|
||||
}
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"message": "Data message sent"
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, a room must be created and the `ROOM_NAME` variable must be set to the name of the room.
|
||||
|
||||
- **Delete room**: Deletes a room.
|
||||
|
||||
- **Method**: `DELETE`
|
||||
- **URL**: `{{ROOM_URL}}/{{ROOM_NAME}}`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"message": "Room deleted"
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, a room must be created and the `ROOM_NAME` variable must be set to the name of the room.
|
||||
|
||||
- **List participants**: Retrieves the list of participants in a room.
|
||||
|
||||
- **Method**: `GET`
|
||||
- **URL**: `{{ROOM_URL}}/{{ROOM_NAME}}/participants`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"participants": [...]
|
||||
}
|
||||
```
|
||||
|
||||
- **Get participant**: Retrieves information about a specific participant.
|
||||
|
||||
- **Method**: `GET`
|
||||
- **URL**: `{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"participant": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join a room using one of the [application clients](../application-client) and set the `ROOM_NAME` and `PARTICIPANT_IDENTITY` correctly.
|
||||
|
||||
- **Update participant**: Updates metadata of a participant.
|
||||
|
||||
- **Method**: `PATCH`
|
||||
- **URL**: `{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"metadata": "Some data"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"participant": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join a room using one of the [application clients](../application-client) and set the `ROOM_NAME` and `PARTICIPANT_IDENTITY` correctly.
|
||||
|
||||
- **Delete participant**: Removes a participant from a room.
|
||||
|
||||
- **Method**: `DELETE`
|
||||
- **URL**: `{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"message": "Participant removed"
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join a room using one of the [application clients](../application-client) and set the `ROOM_NAME` and `PARTICIPANT_IDENTITY` correctly.
|
||||
|
||||
- **Mute track**: Mutes a specific track for a participant.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}/mute`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"trackId": "{{TRACK_ID}}"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"track": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join a room using one of the [application clients](../application-client) and set the `ROOM_NAME` and `PARTICIPANT_IDENTITY` correctly. Then, set the `TRACK_ID` variable to the ID of one of the participant's tracks obtained from the response of the `Get participant` request.
|
||||
|
||||
- **Subscribe to tracks**: Subscribes a participant to specific tracks.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}/subscribe`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"trackIds": ["{{TRACK_ID}}"]
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"message": "Participant subscribed to tracks"
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join two participants in a room using one of the [application clients](../application-client) and set the `ROOM_NAME` and `PARTICIPANT_IDENTITY` correctly. Then, set the `TRACK_ID` variable to the ID of one of the other participant's tracks obtained from the response of the `Get participant` request.
|
||||
|
||||
- **Unsubscribe from tracks**: Unsubscribes a participant from specific tracks.
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}/unsubscribe`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"trackIds": ["{{TRACK_ID}}"]
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"message": "Participant unsubscribed from tracks"
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join two participants in a room using one of the [application clients](../application-client) and set the `ROOM_NAME` and `PARTICIPANT_IDENTITY` correctly. Then, set the `TRACK_ID` variable to the ID of one of the other participant's tracks obtained from the response of the `Get participant` request.
|
||||
|
||||
### Egress requests
|
||||
|
||||
The `Egress` folder contains the following requests:
|
||||
|
||||
- **Create RoomComposite egress**: Starts recording a room with a composite layout.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{EGRESS_URL}}/room-composite`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"egress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join a room using one of the [application clients](../application-client) and set the `ROOM_NAME` correctly.
|
||||
|
||||
- **Create stream egress**: Starts streaming a room to an external RTMP server.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{EGRESS_URL}}/stream`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}",
|
||||
"streamUrl": "rtmp://live.twitch.tv/app/stream-key"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"egress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join a room using one of the [application clients](../application-client) and set the `ROOM_NAME` correctly.
|
||||
|
||||
- **Create Participant egress**: Starts recording a specific participant's tracks.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{EGRESS_URL}}/participant`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}",
|
||||
"participantIdentity": "{{PARTICIPANT_IDENTITY}}"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"egress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join a room using one of the [application clients](../application-client) and set the `ROOM_NAME` and `PARTICIPANT_IDENTITY` correctly.
|
||||
|
||||
- **Create TrackComposite egress**: Starts recording specific audio and video tracks.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{EGRESS_URL}}/track-composite`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}",
|
||||
"videoTrackId": "{{TRACK_ID}}",
|
||||
"audioTrackId": "TR_EXAMPLE"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"egress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join a room using one of the [application clients](../application-client) and set the `ROOM_NAME` and `PARTICIPANT_IDENTITY` correctly.. Then, set the `TRACK_ID` variable to the ID of the video track obtained from the response of the `Get participant` request and the `audioTrackId` parameter to the ID of the audio track.
|
||||
|
||||
- **Create Track egress**: Starts recording a specific track.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{EGRESS_URL}}/track`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}",
|
||||
"trackId": "{{TRACK_ID}}"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"egress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, you need to join a room using one of the [application clients](../application-client) and set the `ROOM_NAME` and `PARTICIPANT_IDENTITY` correctly. Then, set the `TRACK_ID` variable to the ID of one of the tracks obtained from the response of the `Get participant` request.
|
||||
|
||||
- **Create Web egress**: Starts recording a webpage.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{EGRESS_URL}}/web`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"url": "https://openvidu.io"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"egress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
- **List egresses**: Retrieves the list of egresses, optionally filtered by room name, egressID or active status.
|
||||
|
||||
- **Method**: `GET`
|
||||
- **URL**: `{{EGRESS_URL}}?roomName={{ROOM_NAME}}&active=true`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"egresses": [...]
|
||||
}
|
||||
```
|
||||
|
||||
- **Update egress layout**: Changes the layout of an active RoomComposite egress.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{EGRESS_URL}}/{{EGRESS_ID}}/layout`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"layout": "speaker"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"egress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, a RoomComposite egress must be created and the `EGRESS_ID` variable must be set correctly.
|
||||
|
||||
- **Add/remove stream URLs**: Adds or removes RTMP stream URLs in an active egress.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{EGRESS_URL}}/{{EGRESS_ID}}/streams`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"streamUrlsToAdd": ["rtmp://a.rtmp.youtube.com/live2/stream-key"],
|
||||
"streamUrlsToRemove": []
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"egress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, a stream egress must be created and the `EGRESS_ID` variable must be set correctly.
|
||||
|
||||
- **Stop egress**: Terminates an active egress process.
|
||||
|
||||
- **Method**: `DELETE`
|
||||
- **URL**: `{{EGRESS_URL}}/{{EGRESS_ID}}`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"message": "Egress stopped"
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, an egress must be created and the `EGRESS_ID` variable must be set correctly.
|
||||
|
||||
### Ingress requests
|
||||
|
||||
The `Ingress` folder contains the following requests:
|
||||
|
||||
- **Create RTMP ingress**: Creates an RTMP ingress.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{INGRESS_URL}}/rtmp`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}",
|
||||
"participantIdentity": "Ingress-Participant"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"ingress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
- **Create WHIP ingress**: Creates a WHIP ingress.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{INGRESS_URL}}/whip`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}",
|
||||
"participantIdentity": "Ingress-Participant"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"ingress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
- **Create URL ingress**: Creates a URL ingress.
|
||||
|
||||
- **Method**: `POST`
|
||||
- **URL**: `{{INGRESS_URL}}/url`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}",
|
||||
"participantIdentity": "Ingress-Participant",
|
||||
"url": "http://playertest.longtailvideo.com/adaptive/wowzaid3/playlist.m3u8"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"ingress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
- **List ingresses**: Retrieves the list of ingresses, optionally filtered by room name or ingress ID.
|
||||
|
||||
- **Method**: `GET`
|
||||
- **URL**: `{{INGRESS_URL}}?roomName={{ROOM_NAME}}`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"ingresses": [...]
|
||||
}
|
||||
```
|
||||
|
||||
- **Update ingress**: Updates an existing ingress.
|
||||
|
||||
- **Method**: `PATCH`
|
||||
- **URL**: `{{INGRESS_URL}}/{{INGRESS_ID}}`
|
||||
- **Body**:
|
||||
```json
|
||||
{
|
||||
"roomName": "{{ROOM_NAME}}"
|
||||
}
|
||||
```
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"ingress": {...}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, an ingress must be created and the `INGRESS_ID` variable must be set correctly.
|
||||
|
||||
- **Delete ingress**: Deletes an existing ingress.
|
||||
|
||||
- **Method**: `DELETE`
|
||||
- **URL**: `{{INGRESS_URL}}/{{INGRESS_ID}}`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"message": "Ingress deleted"
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Before sending previous request, an ingress must be created and the `INGRESS_ID` variable must be set correctly.
|
||||
@ -0,0 +1,797 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "f3c07b39-211a-4128-9c45-45cf7e7cfdfa",
|
||||
"name": "OpenVidu Server Tutorials",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||
"_exporter_id": "20220126"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Room",
|
||||
"item": [
|
||||
{
|
||||
"name": "Create room",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "List rooms",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}?roomName={{ROOM_NAME}}",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "roomName",
|
||||
"value": "{{ROOM_NAME}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Update room metadata",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"metadata\": \"Some data\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}/{{ROOM_NAME}}/metadata",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{ROOM_NAME}}",
|
||||
"metadata"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Send data",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"data\": {\n \"some\": \"data\"\n }\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}/{{ROOM_NAME}}/send-data",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{ROOM_NAME}}",
|
||||
"send-data"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Delete room",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}/{{ROOM_NAME}}",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{ROOM_NAME}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "List participants",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}/{{ROOM_NAME}}/participants",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{ROOM_NAME}}",
|
||||
"participants"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get participant",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{ROOM_NAME}}",
|
||||
"participants",
|
||||
"{{PARTICIPANT_IDENTITY}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Update participant",
|
||||
"request": {
|
||||
"method": "PATCH",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"metadata\": \"Some data\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{ROOM_NAME}}",
|
||||
"participants",
|
||||
"{{PARTICIPANT_IDENTITY}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Delete participant",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{ROOM_NAME}}",
|
||||
"participants",
|
||||
"{{PARTICIPANT_IDENTITY}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Mute track",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"trackId\": \"{{TRACK_ID}}\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}/mute",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{ROOM_NAME}}",
|
||||
"participants",
|
||||
"{{PARTICIPANT_IDENTITY}}",
|
||||
"mute"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Subscribe to tracks",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"trackIds\": [\"{{TRACK_ID}}\"]\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}/subscribe",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{ROOM_NAME}}",
|
||||
"participants",
|
||||
"{{PARTICIPANT_IDENTITY}}",
|
||||
"subscribe"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Unsubscribe to tracks",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"trackIds\": [\"{{TRACK_ID}}\"]\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{ROOM_URL}}/{{ROOM_NAME}}/participants/{{PARTICIPANT_IDENTITY}}/unsubscribe",
|
||||
"host": [
|
||||
"{{ROOM_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{ROOM_NAME}}",
|
||||
"participants",
|
||||
"{{PARTICIPANT_IDENTITY}}",
|
||||
"unsubscribe"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Egress",
|
||||
"item": [
|
||||
{
|
||||
"name": "Create RoomComposite egress",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{EGRESS_URL}}/room-composite",
|
||||
"host": [
|
||||
"{{EGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"room-composite"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create stream egress",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\",\n \"streamUrl\": \"rtmp://live.twitch.tv/app/stream-key\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{EGRESS_URL}}/stream",
|
||||
"host": [
|
||||
"{{EGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"stream"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create Participant egress",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\",\n \"participantIdentity\": \"{{PARTICIPANT_IDENTITY}}\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{EGRESS_URL}}/participant",
|
||||
"host": [
|
||||
"{{EGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"participant"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create TrackComposite egress",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\",\n \"videoTrackId\": \"{{TRACK_ID}}\",\n \"audioTrackId\": \"TR_AMzNsuvnjz8UHm\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{EGRESS_URL}}/track-composite",
|
||||
"host": [
|
||||
"{{EGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"track-composite"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create Track egress",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\",\n \"trackId\": \"{{TRACK_ID}}\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{EGRESS_URL}}/track",
|
||||
"host": [
|
||||
"{{EGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"track"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create Web egress",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"url\": \"https://openvidu.io\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{EGRESS_URL}}/web",
|
||||
"host": [
|
||||
"{{EGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"web"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "List egresses",
|
||||
"protocolProfileBehavior": {
|
||||
"disableBodyPruning": true
|
||||
},
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{EGRESS_URL}}?roomName={{ROOM_NAME}}&active=true",
|
||||
"host": [
|
||||
"{{EGRESS_URL}}"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "egressId",
|
||||
"value": "{{EGRESS_ID}}",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "roomName",
|
||||
"value": "{{ROOM_NAME}}"
|
||||
},
|
||||
{
|
||||
"key": "active",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Update egress layout",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"layout\": \"speaker\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{EGRESS_URL}}/{{EGRESS_ID}}/layout",
|
||||
"host": [
|
||||
"{{EGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{EGRESS_ID}}",
|
||||
"layout"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Add/remove stream URLs",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"streamUrlsToAdd\": [\"rtmp://a.rtmp.youtube.com/live2/stream-key\"],\n \"streamUrlsToRemove\": []\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{EGRESS_URL}}/{{EGRESS_ID}}/streams",
|
||||
"host": [
|
||||
"{{EGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{EGRESS_ID}}",
|
||||
"streams"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Stop egress",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{EGRESS_URL}}/{{EGRESS_ID}}",
|
||||
"host": [
|
||||
"{{EGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{EGRESS_ID}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Ingress",
|
||||
"item": [
|
||||
{
|
||||
"name": "Create RTMP ingress",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\",\n \"participantIdentity\": \"Ingress-Participant\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{INGRESS_URL}}/rtmp",
|
||||
"host": [
|
||||
"{{INGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"rtmp"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create WHIP ingress",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\",\n \"participantIdentity\": \"Ingress-Participant\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{INGRESS_URL}}/whip",
|
||||
"host": [
|
||||
"{{INGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"whip"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create URL ingress",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\",\n \"participantIdentity\": \"Ingress-Participant\",\n \"url\": \"http://playertest.longtailvideo.com/adaptive/wowzaid3/playlist.m3u8\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{INGRESS_URL}}/url",
|
||||
"host": [
|
||||
"{{INGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"url"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "List ingresses",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{INGRESS_URL}}?roomName={{ROOM_NAME}}",
|
||||
"host": [
|
||||
"{{INGRESS_URL}}"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "ingressId",
|
||||
"value": "{{INGRESS_ID}}",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "roomName",
|
||||
"value": "{{ROOM_NAME}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Update ingress",
|
||||
"request": {
|
||||
"method": "PATCH",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{INGRESS_URL}}/{{INGRESS_ID}}",
|
||||
"host": [
|
||||
"{{INGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{INGRESS_ID}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Delete ingress",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{INGRESS_URL}}/{{INGRESS_ID}}",
|
||||
"host": [
|
||||
"{{INGRESS_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"{{INGRESS_ID}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Create token",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"roomName\": \"{{ROOM_NAME}}\",\n \"participantName\": \"{{PARTICIPANT_IDENTITY}}\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/token",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"token"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "BASE_URL",
|
||||
"value": "http://localhost:6080",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "ROOM_URL",
|
||||
"value": "{{BASE_URL}}/rooms",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "EGRESS_URL",
|
||||
"value": "{{BASE_URL}}/egresses",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "INGRESS_URL",
|
||||
"value": "{{BASE_URL}}/ingresses",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "ROOM_NAME",
|
||||
"value": "Test Room",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "PARTICIPANT_IDENTITY",
|
||||
"value": "Participant1",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "TRACK_ID",
|
||||
"value": "TR_VCfQLUfBeb8YpF",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "EGRESS_ID",
|
||||
"value": "EG_rvEMBCq9vdXA",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "INGRESS_ID",
|
||||
"value": "IN_bjXDVmCnFqev",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
# Basic Go
|
||||
# OpenVidu Go
|
||||
|
||||
Basic server application built for Go with Gin. It internally uses [livekit-server-sdk-go](https://pkg.go.dev/github.com/livekit/server-sdk-go).
|
||||
OpenVidu server application built for Go with Gin. It internally uses [livekit-server-sdk-go](https://pkg.go.dev/github.com/livekit/server-sdk-go).
|
||||
|
||||
For further information, check the [tutorial documentation](https://livekit-tutorials.openvidu.io/tutorials/application-server/go/).
|
||||
|
||||
|
||||
34
application-server/go/config/config.go
Normal file
34
application-server/go/config/config.go
Normal file
@ -0,0 +1,34 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var (
|
||||
ServerPort string
|
||||
LivekitUrl string
|
||||
LivekitApiKey string
|
||||
LivekitApiSecret string
|
||||
)
|
||||
|
||||
func LoadEnv() {
|
||||
// Load environment variables from .env file
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Println("Error loading .env file")
|
||||
}
|
||||
|
||||
ServerPort = getEnv("SERVER_PORT", "6080")
|
||||
LivekitUrl = getEnv("LIVEKIT_URL", "http://localhost:7880")
|
||||
LivekitApiKey = getEnv("LIVEKIT_API", "devkey")
|
||||
LivekitApiSecret = getEnv("LIVEKIT_API_SECRET", "secret")
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
335
application-server/go/controllers/egress_controller.go
Normal file
335
application-server/go/controllers/egress_controller.go
Normal file
@ -0,0 +1,335 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"openvidu/go/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
lksdk "github.com/livekit/server-sdk-go/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
egressClient *lksdk.EgressClient
|
||||
)
|
||||
|
||||
func EgressRoutes(router *gin.Engine) {
|
||||
// Initialize egress client
|
||||
egressClient = lksdk.NewEgressClient(config.LivekitUrl, config.LivekitApiKey, config.LivekitApiSecret)
|
||||
|
||||
egressGroup := router.Group("/egresses")
|
||||
{
|
||||
egressGroup.POST("/room-composite", createRoomCompositeEgress)
|
||||
egressGroup.POST("/stream", createStreamEgress)
|
||||
egressGroup.POST("/participant", createParticipantEgress)
|
||||
egressGroup.POST("/track-composite", createTrackCompositeEgress)
|
||||
egressGroup.POST("/track", createTrackEgress)
|
||||
egressGroup.POST("/web", createWebEgress)
|
||||
egressGroup.GET("/", listEgresses)
|
||||
egressGroup.POST("/:egressId/layout", updateEgressLayout)
|
||||
egressGroup.POST("/:egressId/streams", updateEgressStreams)
|
||||
egressGroup.DELETE("/:egressId", stopEgress)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new RoomComposite egress
|
||||
func createRoomCompositeEgress(c *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName' is required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.RoomCompositeEgressRequest{
|
||||
RoomName: body.RoomName,
|
||||
Layout: "grid",
|
||||
FileOutputs: []*livekit.EncodedFileOutput{
|
||||
{
|
||||
FileType: livekit.EncodedFileType_MP4,
|
||||
Filepath: "{room_name}-{room_id}-{time}",
|
||||
},
|
||||
},
|
||||
}
|
||||
egress, err := egressClient.StartRoomCompositeEgress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error creating RoomComposite egress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"egress": egress})
|
||||
}
|
||||
|
||||
// Create a new RoomComposite egress to stream to a URL
|
||||
func createStreamEgress(c *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
StreamUrl string `json:"streamUrl" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName' and 'streamUrl' are required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.RoomCompositeEgressRequest{
|
||||
RoomName: body.RoomName,
|
||||
Layout: "grid",
|
||||
StreamOutputs: []*livekit.StreamOutput{
|
||||
{
|
||||
Protocol: livekit.StreamProtocol_RTMP,
|
||||
Urls: []string{body.StreamUrl},
|
||||
},
|
||||
},
|
||||
}
|
||||
egress, err := egressClient.StartRoomCompositeEgress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error creating RoomComposite egress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"egress": egress})
|
||||
}
|
||||
|
||||
// Create a new Participant egress
|
||||
func createParticipantEgress(c *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
ParticipantIdentity string `json:"participantIdentity" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName' and 'participantIdentity' are required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.ParticipantEgressRequest{
|
||||
RoomName: body.RoomName,
|
||||
Identity: body.ParticipantIdentity,
|
||||
FileOutputs: []*livekit.EncodedFileOutput{
|
||||
{
|
||||
FileType: livekit.EncodedFileType_MP4,
|
||||
Filepath: "{room_name}-{room_id}-{publisher_identity}-{time}",
|
||||
},
|
||||
},
|
||||
}
|
||||
egress, err := egressClient.StartParticipantEgress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error creating Participant egress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"egress": egress})
|
||||
}
|
||||
|
||||
// Create a new TrackComposite egress
|
||||
func createTrackCompositeEgress(c *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
VideoTrackId string `json:"videoTrackId" binding:"required"`
|
||||
AudioTrackId string `json:"audioTrackId" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName', 'videoTrackId' and 'audioTrackId' are required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.TrackCompositeEgressRequest{
|
||||
RoomName: body.RoomName,
|
||||
VideoTrackId: body.VideoTrackId,
|
||||
AudioTrackId: body.AudioTrackId,
|
||||
FileOutputs: []*livekit.EncodedFileOutput{
|
||||
{
|
||||
FileType: livekit.EncodedFileType_MP4,
|
||||
Filepath: "{room_name}-{room_id}-{publisher_identity}-{time}",
|
||||
},
|
||||
},
|
||||
}
|
||||
egress, err := egressClient.StartTrackCompositeEgress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error creating TrackComposite egress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"egress": egress})
|
||||
}
|
||||
|
||||
// Create a new Track egress
|
||||
func createTrackEgress(c *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
TrackId string `json:"trackId" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName' and 'trackId' are required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.TrackEgressRequest{
|
||||
RoomName: body.RoomName,
|
||||
TrackId: body.TrackId,
|
||||
Output: &livekit.TrackEgressRequest_File{
|
||||
File: &livekit.DirectFileOutput{
|
||||
Filepath: "{room_name}-{room_id}-{publisher_identity}-{track_source}-{track_id}-{time}",
|
||||
},
|
||||
},
|
||||
}
|
||||
egress, err := egressClient.StartTrackEgress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error creating Track egress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"egress": egress})
|
||||
}
|
||||
|
||||
// Create a new Web egress
|
||||
func createWebEgress(c *gin.Context) {
|
||||
var body struct {
|
||||
Url string `json:"url" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName' is required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.WebEgressRequest{
|
||||
Url: body.Url,
|
||||
FileOutputs: []*livekit.EncodedFileOutput{
|
||||
{
|
||||
FileType: livekit.EncodedFileType_MP4,
|
||||
Filepath: "{time}",
|
||||
},
|
||||
},
|
||||
}
|
||||
egress, err := egressClient.StartWebEgress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error creating Web egress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"egress": egress})
|
||||
}
|
||||
|
||||
// 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
|
||||
func listEgresses(c *gin.Context) {
|
||||
egressId := c.Query("egressId")
|
||||
roomName := c.Query("roomName")
|
||||
active := c.Query("active") == "true"
|
||||
|
||||
req := &livekit.ListEgressRequest{
|
||||
EgressId: egressId,
|
||||
RoomName: roomName,
|
||||
Active: active,
|
||||
}
|
||||
res, err := egressClient.ListEgress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error listing egresses"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
egresses := res.Items
|
||||
if egresses == nil {
|
||||
egresses = []*livekit.EgressInfo{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"egresses": egresses})
|
||||
}
|
||||
|
||||
// Update egress layout
|
||||
func updateEgressLayout(c *gin.Context) {
|
||||
egressId := c.Param("egressId")
|
||||
|
||||
var body struct {
|
||||
Layout string `json:"layout" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'layout' is required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.UpdateLayoutRequest{
|
||||
EgressId: egressId,
|
||||
Layout: body.Layout,
|
||||
}
|
||||
egress, err := egressClient.UpdateLayout(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error updating egress layout"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"egress": egress})
|
||||
}
|
||||
|
||||
// Add/remove stream URLs to an egress
|
||||
func updateEgressStreams(c *gin.Context) {
|
||||
egressId := c.Param("egressId")
|
||||
|
||||
var body struct {
|
||||
StreamUrlsToAdd []string `json:"streamUrlsToAdd"`
|
||||
StreamUrlsToRemove []string `json:"streamUrlsToRemove"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'streamUrlsToAdd' and 'streamUrlsToRemove' are required and must be arrays"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.UpdateStreamRequest{
|
||||
EgressId: egressId,
|
||||
AddOutputUrls: body.StreamUrlsToAdd,
|
||||
RemoveOutputUrls: body.StreamUrlsToRemove,
|
||||
}
|
||||
egress, err := egressClient.UpdateStream(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error updating egress streams"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"egress": egress})
|
||||
}
|
||||
|
||||
// Stop an egress
|
||||
func stopEgress(c *gin.Context) {
|
||||
egressId := c.Param("egressId")
|
||||
|
||||
req := &livekit.StopEgressRequest{
|
||||
EgressId: egressId,
|
||||
}
|
||||
_, err := egressClient.StopEgress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error stopping egress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Egress stopped"})
|
||||
}
|
||||
192
application-server/go/controllers/ingress_controller.go
Normal file
192
application-server/go/controllers/ingress_controller.go
Normal file
@ -0,0 +1,192 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"openvidu/go/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
lksdk "github.com/livekit/server-sdk-go/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
ingressClient *lksdk.IngressClient
|
||||
)
|
||||
|
||||
func IngressRoutes(router *gin.Engine) {
|
||||
// Initialize ingress client
|
||||
ingressClient = lksdk.NewIngressClient(config.LivekitUrl, config.LivekitApiKey, config.LivekitApiSecret)
|
||||
|
||||
ingressGroup := router.Group("/ingresses")
|
||||
{
|
||||
ingressGroup.POST("/rtmp", createRTMPIngress)
|
||||
ingressGroup.POST("/whip", createWHIPIngress)
|
||||
ingressGroup.POST("/url", createURLIngress)
|
||||
ingressGroup.GET("/", listIngresses)
|
||||
ingressGroup.PATCH("/:ingressId", updateIngress)
|
||||
ingressGroup.DELETE("/:ingressId", deleteIngress)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new RTMP ingress
|
||||
func createRTMPIngress(c *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
ParticipantIdentity string `json:"participantIdentity" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName' and 'participantIdentity' are required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.CreateIngressRequest{
|
||||
InputType: livekit.IngressInput_RTMP_INPUT,
|
||||
Name: "rtmp-ingress",
|
||||
RoomName: body.RoomName,
|
||||
ParticipantIdentity: body.ParticipantIdentity,
|
||||
}
|
||||
ingress, err := ingressClient.CreateIngress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error creating RTMP ingress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"ingress": ingress})
|
||||
}
|
||||
|
||||
// Create a new WHIP ingress
|
||||
func createWHIPIngress(c *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
ParticipantIdentity string `json:"participantIdentity" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName' and 'participantIdentity' are required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.CreateIngressRequest{
|
||||
InputType: livekit.IngressInput_WHIP_INPUT,
|
||||
Name: "whip-ingress",
|
||||
RoomName: body.RoomName,
|
||||
ParticipantIdentity: body.ParticipantIdentity,
|
||||
}
|
||||
ingress, err := ingressClient.CreateIngress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error creating WHIP ingress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"ingress": ingress})
|
||||
}
|
||||
|
||||
// Create a new URL ingress
|
||||
func createURLIngress(c *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
ParticipantIdentity string `json:"participantIdentity" binding:"required"`
|
||||
Url string `json:"url" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName', 'participantIdentity' and 'url' are required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.CreateIngressRequest{
|
||||
InputType: livekit.IngressInput_URL_INPUT,
|
||||
Name: "url-ingress",
|
||||
RoomName: body.RoomName,
|
||||
ParticipantIdentity: body.ParticipantIdentity,
|
||||
Url: body.Url,
|
||||
}
|
||||
ingress, err := ingressClient.CreateIngress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error creating URL ingress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"ingress": ingress})
|
||||
}
|
||||
|
||||
// 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
|
||||
func listIngresses(c *gin.Context) {
|
||||
ingressId := c.Query("ingressId")
|
||||
roomName := c.Query("roomName")
|
||||
|
||||
req := &livekit.ListIngressRequest{
|
||||
IngressId: ingressId,
|
||||
RoomName: roomName,
|
||||
}
|
||||
res, err := ingressClient.ListIngress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error listing ingresses"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
ingresses := res.Items
|
||||
if ingresses == nil {
|
||||
ingresses = []*livekit.IngressInfo{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ingresses": ingresses})
|
||||
}
|
||||
|
||||
// Update ingress
|
||||
func updateIngress(c *gin.Context) {
|
||||
ingressId := c.Param("ingressId")
|
||||
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName' is required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.UpdateIngressRequest{
|
||||
IngressId: ingressId,
|
||||
Name: "updated-ingress",
|
||||
RoomName: body.RoomName,
|
||||
}
|
||||
ingress, err := ingressClient.UpdateIngress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error updating ingress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ingress": ingress})
|
||||
}
|
||||
|
||||
// Delete ingress
|
||||
func deleteIngress(c *gin.Context) {
|
||||
ingressId := c.Param("ingressId")
|
||||
|
||||
req := &livekit.DeleteIngressRequest{
|
||||
IngressId: ingressId,
|
||||
}
|
||||
_, err := ingressClient.DeleteIngress(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error deleting ingress"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Ingress deleted"})
|
||||
}
|
||||
365
application-server/go/controllers/room_controller.go
Normal file
365
application-server/go/controllers/room_controller.go
Normal file
@ -0,0 +1,365 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"openvidu/go/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
lksdk "github.com/livekit/server-sdk-go/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
roomClient *lksdk.RoomServiceClient
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
func RoomRoutes(router *gin.Engine) {
|
||||
// Initialize RoomServiceClient
|
||||
roomClient = lksdk.NewRoomServiceClient(config.LivekitUrl, config.LivekitApiKey, config.LivekitApiSecret)
|
||||
|
||||
roomGroup := router.Group("/rooms")
|
||||
{
|
||||
roomGroup.POST("", createRoom)
|
||||
roomGroup.GET("", listRooms)
|
||||
roomGroup.POST("/:roomName/metadata", updateRoomMetadata)
|
||||
roomGroup.POST("/:roomName/send-data", sendData)
|
||||
roomGroup.DELETE("/:roomName", deleteRoom)
|
||||
roomGroup.GET("/:roomName/participants", listParticipants)
|
||||
roomGroup.GET("/:roomName/participants/:participantIdentity", getParticipant)
|
||||
roomGroup.PATCH("/:roomName/participants/:participantIdentity", updateParticipant)
|
||||
roomGroup.DELETE("/:roomName/participants/:participantIdentity", removeParticipant)
|
||||
roomGroup.POST("/:roomName/participants/:participantIdentity/mute", muteParticipant)
|
||||
roomGroup.POST("/:roomName/participants/:participantIdentity/subscribe", subscribeParticipant)
|
||||
roomGroup.POST("/:roomName/participants/:participantIdentity/unsubscribe", unsubscribeParticipant)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new room
|
||||
func createRoom(c *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName' is required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.CreateRoomRequest{
|
||||
Name: body.RoomName,
|
||||
}
|
||||
room, err := roomClient.CreateRoom(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error creating room"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"room": room})
|
||||
}
|
||||
|
||||
// List rooms. If a room name is provided, only that room is listed
|
||||
func listRooms(c *gin.Context) {
|
||||
roomName := c.Query("roomName")
|
||||
|
||||
var roomNames []string
|
||||
if roomName != "" {
|
||||
roomNames = []string{roomName}
|
||||
}
|
||||
|
||||
req := &livekit.ListRoomsRequest{
|
||||
Names: roomNames,
|
||||
}
|
||||
res, err := roomClient.ListRooms(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error listing rooms"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
rooms := res.Rooms
|
||||
if rooms == nil {
|
||||
rooms = []*livekit.Room{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"rooms": rooms})
|
||||
}
|
||||
|
||||
// Update room metadata
|
||||
func updateRoomMetadata(c *gin.Context) {
|
||||
roomName := c.Param("roomName")
|
||||
|
||||
var body struct {
|
||||
Metadata string `json:"metadata" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'metadata' is required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.UpdateRoomMetadataRequest{
|
||||
Room: roomName,
|
||||
Metadata: body.Metadata,
|
||||
}
|
||||
room, err := roomClient.UpdateRoomMetadata(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error updating room metadata"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"room": room})
|
||||
}
|
||||
|
||||
// Send data message to participants in a room
|
||||
func sendData(c *gin.Context) {
|
||||
roomName := c.Param("roomName")
|
||||
|
||||
var body struct {
|
||||
Data json.RawMessage `json:"data" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'data' is required"})
|
||||
return
|
||||
}
|
||||
|
||||
rawData, err := json.Marshal(body.Data)
|
||||
if err != nil {
|
||||
log.Println("Error encoding data message:", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": "Error encoding data message"})
|
||||
return
|
||||
}
|
||||
|
||||
topic := "chat"
|
||||
req := &livekit.SendDataRequest{
|
||||
Room: roomName,
|
||||
Data: rawData,
|
||||
Kind: livekit.DataPacket_RELIABLE,
|
||||
Topic: &topic,
|
||||
DestinationIdentities: []string{},
|
||||
}
|
||||
_, err = roomClient.SendData(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error sending data message"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Data message sent"})
|
||||
}
|
||||
|
||||
// Delete a room
|
||||
func deleteRoom(c *gin.Context) {
|
||||
roomName := c.Param("roomName")
|
||||
|
||||
req := &livekit.DeleteRoomRequest{
|
||||
Room: roomName,
|
||||
}
|
||||
_, err := roomClient.DeleteRoom(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error deleting room"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Room deleted"})
|
||||
}
|
||||
|
||||
// List participants in a room
|
||||
func listParticipants(c *gin.Context) {
|
||||
roomName := c.Param("roomName")
|
||||
|
||||
req := &livekit.ListParticipantsRequest{
|
||||
Room: roomName,
|
||||
}
|
||||
res, err := roomClient.ListParticipants(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error listing participants"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
participants := res.Participants
|
||||
if participants == nil {
|
||||
participants = []*livekit.ParticipantInfo{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"participants": participants})
|
||||
}
|
||||
|
||||
// Get a participant in a room
|
||||
func getParticipant(c *gin.Context) {
|
||||
roomName := c.Param("roomName")
|
||||
participantIdentity := c.Param("participantIdentity")
|
||||
|
||||
req := &livekit.RoomParticipantIdentity{
|
||||
Room: roomName,
|
||||
Identity: participantIdentity,
|
||||
}
|
||||
participant, err := roomClient.GetParticipant(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error getting participant"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"participant": participant})
|
||||
}
|
||||
|
||||
// Update a participant in a room
|
||||
func updateParticipant(c *gin.Context) {
|
||||
roomName := c.Param("roomName")
|
||||
participantIdentity := c.Param("participantIdentity")
|
||||
|
||||
var body struct {
|
||||
Metadata string `json:"metadata" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'metadata' is required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.UpdateParticipantRequest{
|
||||
Room: roomName,
|
||||
Identity: participantIdentity,
|
||||
Metadata: body.Metadata,
|
||||
Permission: &livekit.ParticipantPermission{
|
||||
CanPublish: false,
|
||||
CanSubscribe: true,
|
||||
},
|
||||
}
|
||||
participant, err := roomClient.UpdateParticipant(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error updating participant"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"participant": participant})
|
||||
}
|
||||
|
||||
// Remove a participant from a room
|
||||
func removeParticipant(c *gin.Context) {
|
||||
roomName := c.Param("roomName")
|
||||
participantIdentity := c.Param("participantIdentity")
|
||||
|
||||
req := &livekit.RoomParticipantIdentity{
|
||||
Room: roomName,
|
||||
Identity: participantIdentity,
|
||||
}
|
||||
_, err := roomClient.RemoveParticipant(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error removing participant"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Participant removed"})
|
||||
}
|
||||
|
||||
// Mute published track of a participant in a room
|
||||
func muteParticipant(c *gin.Context) {
|
||||
roomName := c.Param("roomName")
|
||||
participantIdentity := c.Param("participantIdentity")
|
||||
|
||||
var body struct {
|
||||
TrackId string `json:"trackId" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'trackSid' is required"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.MuteRoomTrackRequest{
|
||||
Room: roomName,
|
||||
Identity: participantIdentity,
|
||||
TrackSid: body.TrackId,
|
||||
Muted: true,
|
||||
}
|
||||
res, err := roomClient.MutePublishedTrack(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error muting track"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"track": res.Track})
|
||||
}
|
||||
|
||||
// Subscribe participant to tracks in a room
|
||||
func subscribeParticipant(c *gin.Context) {
|
||||
roomName := c.Param("roomName")
|
||||
participantIdentity := c.Param("participantIdentity")
|
||||
|
||||
var body struct {
|
||||
TrackIds []string `json:"trackIds" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil || len(body.TrackIds) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'trackIds' is required and must be an array"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.UpdateSubscriptionsRequest{
|
||||
Room: roomName,
|
||||
Identity: participantIdentity,
|
||||
TrackSids: body.TrackIds,
|
||||
Subscribe: true,
|
||||
}
|
||||
_, err := roomClient.UpdateSubscriptions(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error subscribing participant to tracks"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Participant subscribed to tracks"})
|
||||
}
|
||||
|
||||
// Unsubscribe participant from tracks in a room
|
||||
func unsubscribeParticipant(c *gin.Context) {
|
||||
roomName := c.Param("roomName")
|
||||
participantIdentity := c.Param("participantIdentity")
|
||||
|
||||
var body struct {
|
||||
TrackIds []string `json:"trackIds" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil || len(body.TrackIds) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'trackIds' is required and must be an array"})
|
||||
return
|
||||
}
|
||||
|
||||
req := &livekit.UpdateSubscriptionsRequest{
|
||||
Room: roomName,
|
||||
Identity: participantIdentity,
|
||||
TrackSids: body.TrackIds,
|
||||
Subscribe: false,
|
||||
}
|
||||
_, err := roomClient.UpdateSubscriptions(ctx, req)
|
||||
if err != nil {
|
||||
errorMessage := "Error unsubscribing participant from tracks"
|
||||
log.Println(errorMessage+":", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"errorMessage": errorMessage})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Participant unsubscribed from tracks"})
|
||||
}
|
||||
40
application-server/go/controllers/token_controller.go
Normal file
40
application-server/go/controllers/token_controller.go
Normal file
@ -0,0 +1,40 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"openvidu/go/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/livekit/protocol/auth"
|
||||
)
|
||||
|
||||
func TokenRoutes(router *gin.Engine) {
|
||||
router.POST("/token", createToken)
|
||||
}
|
||||
|
||||
func createToken(c *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName" binding:"required"`
|
||||
ParticipantName string `json:"participantName" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"errorMessage": "'roomName' and 'participantName' are required"})
|
||||
return
|
||||
}
|
||||
|
||||
at := auth.NewAccessToken(config.LivekitApiKey, config.LivekitApiSecret)
|
||||
grant := &auth.VideoGrant{
|
||||
RoomJoin: true,
|
||||
Room: body.RoomName,
|
||||
}
|
||||
at.SetVideoGrant(grant).SetIdentity(body.ParticipantName)
|
||||
|
||||
token, err := at.ToJWT()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"token": token})
|
||||
}
|
||||
34
application-server/go/controllers/webhook_controller.go
Normal file
34
application-server/go/controllers/webhook_controller.go
Normal file
@ -0,0 +1,34 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"openvidu/go/config"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/webhook"
|
||||
)
|
||||
|
||||
var authProvider *auth.SimpleKeyProvider
|
||||
|
||||
func WebhookRoutes(router *gin.Engine) {
|
||||
// Initialize authProvider
|
||||
authProvider = auth.NewSimpleKeyProvider(
|
||||
config.LivekitApiKey, config.LivekitApiSecret,
|
||||
)
|
||||
|
||||
router.POST("/livekit/webhook", receiveWebhook)
|
||||
}
|
||||
|
||||
func receiveWebhook(c *gin.Context) {
|
||||
webhookEvent, err := webhook.ReceiveWebhookEvent(c.Request, authProvider)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error validating webhook event: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
json, _ := json.MarshalIndent(webhookEvent, "", " ")
|
||||
fmt.Println("LiveKit Webhook:\n", string(json))
|
||||
}
|
||||
@ -1,96 +1,101 @@
|
||||
module openvidu/basic-go
|
||||
module openvidu/go
|
||||
|
||||
go 1.22.2
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/cors v1.7.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/gin-contrib/cors v1.7.5
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/livekit/protocol v1.27.0
|
||||
github.com/livekit/protocol v1.39.0
|
||||
github.com/livekit/server-sdk-go/v2 v2.9.0
|
||||
)
|
||||
|
||||
require (
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240401165935-b983156c5e99.1 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 // indirect
|
||||
buf.build/go/protovalidate v0.12.0 // indirect
|
||||
buf.build/go/protoyaml v0.6.0 // indirect
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/bufbuild/protovalidate-go v0.6.1 // indirect
|
||||
github.com/bufbuild/protoyaml-go v0.1.9 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/dennwc/iters v1.1.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/frostbyte73/core v0.0.12 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gammazero/deque v0.2.1 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
||||
github.com/frostbyte73/core v0.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gammazero/deque v1.0.0 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/cel-go v0.20.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/google/cel-go v0.25.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 // indirect
|
||||
github.com/livekit/psrpc v0.6.1-0.20240924010758-9f0a4268a3b9 // indirect
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0 // indirect
|
||||
github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 // indirect
|
||||
github.com/livekit/mediatransportutil v0.0.0-20250519131108-fb90f5acfded // indirect
|
||||
github.com/livekit/psrpc v0.6.1-0.20250511053145-465289d72c3c // indirect
|
||||
github.com/magefile/mage v1.15.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/nats-io/nats.go v1.36.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||
github.com/nats-io/nats.go v1.42.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pion/datachannel v1.5.5 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||
github.com/pion/ice/v2 v2.3.13 // indirect
|
||||
github.com/pion/interceptor v0.1.25 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/mdns v0.0.12 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pion/datachannel v1.5.10 // indirect
|
||||
github.com/pion/dtls/v3 v3.0.6 // indirect
|
||||
github.com/pion/ice/v4 v4.0.10 // indirect
|
||||
github.com/pion/interceptor v0.1.37 // indirect
|
||||
github.com/pion/logging v0.2.3 // indirect
|
||||
github.com/pion/mdns/v2 v2.0.7 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.12 // indirect
|
||||
github.com/pion/rtp v1.8.3 // indirect
|
||||
github.com/pion/sctp v1.8.12 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.6 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.18 // indirect
|
||||
github.com/pion/stun v0.6.1 // indirect
|
||||
github.com/pion/transport/v2 v2.2.3 // indirect
|
||||
github.com/pion/turn/v2 v2.1.3 // indirect
|
||||
github.com/pion/webrtc/v3 v3.2.28 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.6.1 // indirect
|
||||
github.com/pion/rtcp v1.2.15 // indirect
|
||||
github.com/pion/rtp v1.8.15 // indirect
|
||||
github.com/pion/sctp v1.8.39 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.11 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.4 // indirect
|
||||
github.com/pion/stun/v3 v3.0.0 // indirect
|
||||
github.com/pion/transport/v3 v3.0.7 // indirect
|
||||
github.com/pion/turn/v4 v4.0.2 // indirect
|
||||
github.com/pion/webrtc/v4 v4.1.1 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.8.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/wlynxg/anet v0.0.5 // indirect
|
||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.2.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f // indirect
|
||||
google.golang.org/grpc v1.65.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect
|
||||
google.golang.org/grpc v1.72.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@ -1,204 +1,222 @@
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240401165935-b983156c5e99.1 h1:2IGhRovxlsOIQgx2ekZWo4wTPAYpck41+18ICxs37is=
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240401165935-b983156c5e99.1/go.mod h1:Tgn5bgL220vkFOI0KPStlcClPeOJzAv4uT+V8JXGUnw=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 h1:YhMSc48s25kr7kv31Z8vf7sPUIq5YJva9z1mn/hAt0M=
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
|
||||
buf.build/go/protovalidate v0.12.0 h1:4GKJotbspQjRCcqZMGVSuC8SjwZ/FmgtSuKDpKUTZew=
|
||||
buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0=
|
||||
buf.build/go/protoyaml v0.6.0 h1:Nzz1lvcXF8YgNZXk+voPPwdU8FjDPTUV4ndNTXN0n2w=
|
||||
buf.build/go/protoyaml v0.6.0/go.mod h1:RgUOsBu/GYKLDSIRgQXniXbNgFlGEZnQpRAUdLAFV2Q=
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bufbuild/protovalidate-go v0.6.1 h1:uzW8r0CDvqApUChNj87VzZVoQSKhcVdw5UWOE605UIw=
|
||||
github.com/bufbuild/protovalidate-go v0.6.1/go.mod h1:4BR3rKEJiUiTy+sqsusFn2ladOf0kYmA2Reo6BHSBgQ=
|
||||
github.com/bufbuild/protoyaml-go v0.1.9 h1:anV5UtF1Mlvkkgp4NWA6U/zOnJFng8Orq4Vf3ZUQHBU=
|
||||
github.com/bufbuild/protoyaml-go v0.1.9/go.mod h1:KCBItkvZOK/zwGueLdH1Wx1RLyFn5rCH7YjQrdty2Wc=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
|
||||
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dennwc/iters v1.1.0 h1:PsS3DbOU7GxSUQO0e7SGmzHkPhtwOlwbqggJ++Bgnr8=
|
||||
github.com/dennwc/iters v1.1.0/go.mod h1:M9KuuMBeyEXYTmB7EnI9SCyALFCmPWOIxn5W1L0CjGg=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
||||
github.com/docker/cli v26.1.4+incompatible h1:I8PHdc0MtxEADqYJZvhBrW9bo8gawKwwenxRM7/rLu8=
|
||||
github.com/docker/cli v26.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
||||
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/frostbyte73/core v0.0.12 h1:kySA8+Os6eqnPFoExD2T7cehjSAY1MRyIViL0yTy2uc=
|
||||
github.com/frostbyte73/core v0.0.12/go.mod h1:XsOGqrqe/VEV7+8vJ+3a8qnCIXNbKsoEiu/czs7nrcU=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
|
||||
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
|
||||
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
|
||||
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
||||
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/frostbyte73/core v0.1.1 h1:ChhJOR7bAKOCPbA+lqDLE2cGKlCG5JXsDvvQr4YaJIA=
|
||||
github.com/frostbyte73/core v0.1.1/go.mod h1:mhfOtR+xWAvwXiwor7jnqPMnu4fxbv1F2MwZ0BEpzZo=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gammazero/deque v1.0.0 h1:LTmimT8H7bXkkCy6gZX7zNLtkbz4NdS2z8LZuor3j34=
|
||||
github.com/gammazero/deque v1.0.0/go.mod h1:iflpYvtGfM3U8S8j+sZEKIak3SAKYpA5/SQewgfXDKo=
|
||||
github.com/gin-contrib/cors v1.7.5 h1:cXC9SmofOrRg0w9PigwGlHG3ztswH6bqq4vJVXnvYMk=
|
||||
github.com/gin-contrib/cors v1.7.5/go.mod h1:4q3yi7xBEDDWKapjT2o1V7mScKDDr8k+jZ0fSquGoy0=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
|
||||
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY=
|
||||
github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 h1:jm09419p0lqTkDaKb5iXdynYrzB84ErPPO4LbRASk58=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ=
|
||||
github.com/livekit/protocol v1.27.0 h1:qdZ8S4eH11XbBQxpG4eHh9GZC7weyydngWNvH2NTD+w=
|
||||
github.com/livekit/protocol v1.27.0/go.mod h1:nxRzmQBKSYK64gqr7ABWwt78hvrgiO2wYuCojRYb7Gs=
|
||||
github.com/livekit/psrpc v0.6.1-0.20240924010758-9f0a4268a3b9 h1:33oBjGpVD9tYkDXQU42tnHl8eCX9G6PVUToBVuCUyOs=
|
||||
github.com/livekit/psrpc v0.6.1-0.20240924010758-9f0a4268a3b9/go.mod h1:CQUBSPfYYAaevg1TNCc6/aYsa8DJH4jSRFdCeSZk5u0=
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
|
||||
github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 h1:9x+U2HGLrSw5ATTo469PQPkqzdoU7be46ryiCDO3boc=
|
||||
github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20250519131108-fb90f5acfded h1:ylZPdnlX1RW9Z15SD4mp87vT2D2shsk0hpLJwSPcq3g=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20250519131108-fb90f5acfded/go.mod h1:mSNtYzSf6iY9xM3UX42VEI+STHvMgHmrYzEHPcdhB8A=
|
||||
github.com/livekit/protocol v1.39.0 h1:xmpkEr0+29xiAu+Z/m7CmsGuLKHf5ba5+5rkGdMdZEY=
|
||||
github.com/livekit/protocol v1.39.0/go.mod h1:6HPISM0bkTXTk9RIaQTCe0IDbomBPz7Jwp+N3w5sqL0=
|
||||
github.com/livekit/psrpc v0.6.1-0.20250511053145-465289d72c3c h1:WwEr0YBejYbKzk8LSaO9h8h0G9MnE7shyDu8yXQWmEc=
|
||||
github.com/livekit/psrpc v0.6.1-0.20250511053145-465289d72c3c/go.mod h1:kmD+AZPkWu0MaXIMv57jhNlbiSZZ/Jx4bzlxBDVmJes=
|
||||
github.com/livekit/server-sdk-go/v2 v2.9.0 h1:Kan/15kh/aT0Pz8d/YEltp7Sgbpmb4VhHVgCi9Pg6Rw=
|
||||
github.com/livekit/server-sdk-go/v2 v2.9.0/go.mod h1:rDH1cleKLzbH1N/Pve8CGq2MsvQEQJUzEEEaMQTpkmc=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU=
|
||||
github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
|
||||
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
|
||||
github.com/nats-io/nats.go v1.42.0 h1:ynIMupIOvf/ZWH/b2qda6WGKGNSjwOUutTpWRvAmhaM=
|
||||
github.com/nats-io/nats.go v1.42.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/ice/v2 v2.3.13 h1:xOxP+4V9nSDlUaGFRf/LvAuGHDXRcjIdsbbXPK/w7c8=
|
||||
github.com/pion/ice/v2 v2.3.13/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw=
|
||||
github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
|
||||
github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
|
||||
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w=
|
||||
github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA=
|
||||
github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA=
|
||||
github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
|
||||
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
||||
github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E=
|
||||
github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU=
|
||||
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
|
||||
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
||||
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
|
||||
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
|
||||
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
|
||||
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
|
||||
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
||||
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||
github.com/pion/rtcp v1.2.12 h1:bKWiX93XKgDZENEXCijvHRU/wRifm6JV5DGcH6twtSM=
|
||||
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
||||
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||
github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
|
||||
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sctp v1.8.12 h1:2VX50pedElH+is6FI+OKyRTeN5oy4mrk2HjnGa3UCmY=
|
||||
github.com/pion/sctp v1.8.12/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
|
||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo=
|
||||
github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
|
||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
|
||||
github.com/pion/transport/v2 v2.2.3 h1:XcOE3/x41HOSKbl1BfyY1TF1dERx7lVvlMCbXU7kfvA=
|
||||
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||
github.com/pion/turn/v2 v2.1.3 h1:pYxTVWG2gpC97opdRc5IGsQ1lJ9O/IlNhkzj7MMrGAA=
|
||||
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
||||
github.com/pion/webrtc/v3 v3.2.28 h1:ienStxZ6HcjtH2UlmnFpMM0loENiYjaX437uIUpQSKo=
|
||||
github.com/pion/webrtc/v3 v3.2.28/go.mod h1:PNRCEuQlibrmuBhOTnol9j6KkIbUG11aHLEfNpUYey0=
|
||||
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||
github.com/pion/rtp v1.8.15 h1:MuhuGn1cxpVCPLNY1lI7F1tQ8Spntpgf12ob+pOYT8s=
|
||||
github.com/pion/rtp v1.8.15/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
|
||||
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
|
||||
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
||||
github.com/pion/sdp/v3 v3.0.11 h1:VhgVSopdsBKwhCFoyyPmT1fKMeV9nLMrEKxNOdy3IVI=
|
||||
github.com/pion/sdp/v3 v3.0.11/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
||||
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
|
||||
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
|
||||
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
||||
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
|
||||
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
||||
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
||||
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
|
||||
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
|
||||
github.com/pion/webrtc/v4 v4.1.1 h1:PMFPtLg1kpD2pVtun+LGUzA3k54JdFl87WO0Z1+HKug=
|
||||
github.com/pion/webrtc/v4 v4.1.1/go.mod h1:cgEGkcpxGkT6Di2ClBYO5lP9mFXbCfEOrkYUpjjCQO4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
|
||||
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
|
||||
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk=
|
||||
github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@ -206,22 +224,27 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU=
|
||||
github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
@ -235,138 +258,74 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
|
||||
go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f h1:RARaIm8pxYuxyNPbBQf5igT7XdOyCNtat1qAT2ZxjU4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 h1:WvBuA5rjZx9SNIzgcU53OohgZy6lKSus++uY4xLaWKc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:W3S/3np0/dPWsWLi1h/UymYctGXaGBM2StwzD0y140U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
@ -1,84 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"openvidu/go/config"
|
||||
"openvidu/go/controllers"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/webhook"
|
||||
)
|
||||
|
||||
var SERVER_PORT string
|
||||
var LIVEKIT_API_KEY string
|
||||
var LIVEKIT_API_SECRET string
|
||||
|
||||
func createToken(context *gin.Context) {
|
||||
var body struct {
|
||||
RoomName string `json:"roomName"`
|
||||
ParticipantName string `json:"participantName"`
|
||||
}
|
||||
|
||||
if err := context.BindJSON(&body); err != nil {
|
||||
context.JSON(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if body.RoomName == "" || body.ParticipantName == "" {
|
||||
context.JSON(http.StatusBadRequest, gin.H{"errorMessage": "roomName and participantName are required"})
|
||||
return
|
||||
}
|
||||
|
||||
at := auth.NewAccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET)
|
||||
grant := &auth.VideoGrant{
|
||||
RoomJoin: true,
|
||||
Room: body.RoomName,
|
||||
}
|
||||
at.SetVideoGrant(grant).SetIdentity(body.ParticipantName)
|
||||
|
||||
token, err := at.ToJWT()
|
||||
if err != nil {
|
||||
context.JSON(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
context.JSON(http.StatusOK, gin.H{"token": token})
|
||||
}
|
||||
|
||||
func receiveWebhook(context *gin.Context) {
|
||||
authProvider := auth.NewSimpleKeyProvider(
|
||||
LIVEKIT_API_KEY, LIVEKIT_API_SECRET,
|
||||
)
|
||||
event, err := webhook.ReceiveWebhookEvent(context.Request, authProvider)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error validating webhook event: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("LiveKit Webhook", event)
|
||||
}
|
||||
|
||||
func main() {
|
||||
loadEnv()
|
||||
config.LoadEnv()
|
||||
router := gin.Default()
|
||||
router.Use(cors.Default())
|
||||
router.POST("/token", createToken)
|
||||
router.POST("/livekit/webhook", receiveWebhook)
|
||||
router.Run(":" + SERVER_PORT)
|
||||
}
|
||||
|
||||
func loadEnv() {
|
||||
godotenv.Load() // Load environment variables from .env file
|
||||
SERVER_PORT = getEnv("SERVER_PORT", "6080")
|
||||
LIVEKIT_API_KEY = getEnv("LIVEKIT_API", "devkey")
|
||||
LIVEKIT_API_SECRET = getEnv("LIVEKIT_API_SECRET", "secret")
|
||||
}
|
||||
controllers.TokenRoutes(router)
|
||||
controllers.WebhookRoutes(router)
|
||||
controllers.RoomRoutes(router)
|
||||
controllers.EgressRoutes(router)
|
||||
controllers.IngressRoutes(router)
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
router.Run(":" + config.ServerPort)
|
||||
}
|
||||
|
||||
@ -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.5.0</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.9.0</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,404 @@
|
||||
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.HttpStatus;
|
||||
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.status(HttpStatus.CREATED)
|
||||
.body(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.status(HttpStatus.CREATED)
|
||||
.body(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.status(HttpStatus.CREATED)
|
||||
.body(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.status(HttpStatus.CREATED)
|
||||
.body(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.status(HttpStatus.CREATED)
|
||||
.body(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.status(HttpStatus.CREATED)
|
||||
.body(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>> updateEgressStreams(@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,246 @@
|
||||
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.HttpStatus;
|
||||
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.status(HttpStatus.CREATED)
|
||||
.body(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.status(HttpStatus.CREATED)
|
||||
.body(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.status(HttpStatus.CREATED)
|
||||
.body(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 {
|
||||
IngressInfo ingress = ingressClient
|
||||
.updateIngress(ingressId, "updated-ingress", roomName)
|
||||
.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,401 @@
|
||||
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.HttpStatus;
|
||||
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.status(HttpStatus.CREATED)
|
||||
.body(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() {
|
||||
@ -1,6 +1,6 @@
|
||||
# Basic Node
|
||||
# OpenVidu Node
|
||||
|
||||
Basic server application built for Node.js with Express. It internally uses [livekit-server-sdk-js](https://docs.livekit.io/server-sdk-js/).
|
||||
OpenVidu server application built for Node.js with Express. It internally uses [livekit-server-sdk-js](https://docs.livekit.io/server-sdk-js/).
|
||||
|
||||
For further information, check the [tutorial documentation](https://livekit-tutorials.openvidu.io/tutorials/application-server/node/).
|
||||
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
import "dotenv/config";
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import { AccessToken, WebhookReceiver } from "livekit-server-sdk";
|
||||
|
||||
const SERVER_PORT = process.env.SERVER_PORT || 6080;
|
||||
const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY || "devkey";
|
||||
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET || "secret";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.raw({ type: "application/webhook+json" }));
|
||||
|
||||
app.post("/token", async (req, res) => {
|
||||
const roomName = req.body.roomName;
|
||||
const participantName = req.body.participantName;
|
||||
|
||||
if (!roomName || !participantName) {
|
||||
res.status(400).json({ errorMessage: "roomName and participantName are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, {
|
||||
identity: participantName,
|
||||
});
|
||||
at.addGrant({ roomJoin: true, room: roomName });
|
||||
const token = await at.toJwt();
|
||||
res.json({ token });
|
||||
});
|
||||
|
||||
const webhookReceiver = new WebhookReceiver(
|
||||
LIVEKIT_API_KEY,
|
||||
LIVEKIT_API_SECRET
|
||||
);
|
||||
|
||||
app.post("/livekit/webhook", async (req, res) => {
|
||||
try {
|
||||
const event = await webhookReceiver.receive(
|
||||
req.body,
|
||||
req.get("Authorization")
|
||||
);
|
||||
console.log(event);
|
||||
} catch (error) {
|
||||
console.error("Error validating webhook event", error);
|
||||
}
|
||||
res.status(200).send();
|
||||
});
|
||||
|
||||
app.listen(SERVER_PORT, () => {
|
||||
console.log("Server started on port:", SERVER_PORT);
|
||||
});
|
||||
650
application-server/node/package-lock.json
generated
650
application-server/node/package-lock.json
generated
@ -1,29 +1,29 @@
|
||||
{
|
||||
"name": "basic-node",
|
||||
"name": "openvidu-node",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "basic-node",
|
||||
"name": "openvidu-node",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "16.4.5",
|
||||
"express": "5.0.1",
|
||||
"livekit-server-sdk": "^2.7.2"
|
||||
"dotenv": "16.5.0",
|
||||
"express": "5.1.0",
|
||||
"livekit-server-sdk": "2.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
|
||||
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==",
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz",
|
||||
"integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==",
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/@livekit/protocol": {
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.27.0.tgz",
|
||||
"integrity": "sha512-jVb4zljNaYKoLiL5MBjGiO1+QKVsxMqXT/c0dwcKUW7NCLjAZXucoQVV1Y79FCbKwVnOCOtI6wwteEntbfk/Qw==",
|
||||
"version": "1.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.39.0.tgz",
|
||||
"integrity": "sha512-Zar6711kJk1MjI+63DJoyCxv8deNphIIan7tK9C5f+Zy3v2Pvjs6Yz7cFoOeDK/rY7b7deA2wfYxUi2S5XLp7w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^1.10.0"
|
||||
@ -42,90 +42,24 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz",
|
||||
"integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.1.tgz",
|
||||
"integrity": "sha512-PagxbjvuPH6tv0f/kdVbFGcb79D236SLcDTs6DrQ7GizJ88S1UWP4nMXFEo/I4fdhGRGabvFfFjVGm3M7U8JwA==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
||||
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "3.1.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.5.2",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.13.0",
|
||||
"bytes": "^3.1.2",
|
||||
"content-type": "^1.0.5",
|
||||
"debug": "^4.4.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"qs": "^6.14.0",
|
||||
"raw-body": "^3.0.0",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
"type-is": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser/node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
@ -137,17 +71,27 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -208,18 +152,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz",
|
||||
"integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
|
||||
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.6.0"
|
||||
@ -239,12 +183,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
@ -255,23 +199,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@ -281,20 +208,10 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||
"version": "16.5.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
|
||||
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@ -303,6 +220,20 @@
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@ -319,13 +250,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@ -339,6 +267,18 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@ -355,90 +295,64 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz",
|
||||
"integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.0.1",
|
||||
"body-parser": "^2.2.0",
|
||||
"content-disposition": "^1.0.0",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.7.1",
|
||||
"content-type": "^1.0.5",
|
||||
"cookie": "^0.7.1",
|
||||
"cookie-signature": "^1.2.1",
|
||||
"debug": "4.3.6",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "^2.0.0",
|
||||
"fresh": "2.0.0",
|
||||
"http-errors": "2.0.0",
|
||||
"debug": "^4.4.0",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"finalhandler": "^2.1.0",
|
||||
"fresh": "^2.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"merge-descriptors": "^2.0.0",
|
||||
"methods": "~1.1.2",
|
||||
"mime-types": "^3.0.0",
|
||||
"on-finished": "2.4.1",
|
||||
"once": "1.4.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.13.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"router": "^2.0.0",
|
||||
"safe-buffer": "5.2.1",
|
||||
"on-finished": "^2.4.1",
|
||||
"once": "^1.4.0",
|
||||
"parseurl": "^1.3.3",
|
||||
"proxy-addr": "^2.0.7",
|
||||
"qs": "^6.14.0",
|
||||
"range-parser": "^1.2.1",
|
||||
"router": "^2.2.0",
|
||||
"send": "^1.1.0",
|
||||
"serve-static": "^2.1.0",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "^2.0.0",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
"serve-static": "^2.2.0",
|
||||
"statuses": "^2.0.1",
|
||||
"type-is": "^2.0.1",
|
||||
"vary": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz",
|
||||
"integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
||||
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "2.0.1",
|
||||
"unpipe": "~1.0.0"
|
||||
"debug": "^4.4.0",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"parseurl": "^1.3.3",
|
||||
"statuses": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler/node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@ -467,16 +381,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -485,34 +404,23 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -522,9 +430,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -562,12 +470,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz",
|
||||
"integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@ -595,26 +503,27 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "5.9.6",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz",
|
||||
"integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==",
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
|
||||
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/livekit-server-sdk": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/livekit-server-sdk/-/livekit-server-sdk-2.7.2.tgz",
|
||||
"integrity": "sha512-qDNRXeo+WMnY5nKSug7KHJ9er9JIuKi+r7H9ZaSBbmbaOt62i0b4BrHBMFSMr8pAuWzuSxihCFa29q5QvFc5fw==",
|
||||
"version": "2.13.0",
|
||||
"resolved": "https://registry.npmjs.org/livekit-server-sdk/-/livekit-server-sdk-2.13.0.tgz",
|
||||
"integrity": "sha512-fQJI/zEJRPeXKdKMkEfJNYSSnvmuPQsk2Q+X6tPfUrJPy7fnyYPax/icf/CZ8EYZQBhFgSD7WaKOYGSSfGSyZw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@livekit/protocol": "^1.23.0",
|
||||
"@bufbuild/protobuf": "^1.7.2",
|
||||
"@livekit/protocol": "^1.38.0",
|
||||
"camelcase-keys": "^9.0.0",
|
||||
"jose": "^5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=19"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/map-obj": {
|
||||
@ -629,6 +538,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
||||
@ -650,40 +568,31 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.53.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
|
||||
"integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz",
|
||||
"integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
||||
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "^1.53.0"
|
||||
"mime-db": "^1.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
@ -705,9 +614,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
|
||||
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -769,12 +678,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@ -819,34 +728,20 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.0.0.tgz",
|
||||
"integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"array-flatten": "3.0.0",
|
||||
"is-promise": "4.0.0",
|
||||
"methods": "~1.1.2",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "^8.0.0",
|
||||
"setprototypeof": "1.2.0",
|
||||
"utils-merge": "1.0.1"
|
||||
"debug": "^4.4.0",
|
||||
"depd": "^2.0.0",
|
||||
"is-promise": "^4.0.0",
|
||||
"parseurl": "^1.3.3",
|
||||
"path-to-regexp": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
@ -876,19 +771,18 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz",
|
||||
"integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
||||
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.5",
|
||||
"destroy": "^1.2.0",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"fresh": "^0.5.2",
|
||||
"fresh": "^2.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"mime-types": "^3.0.1",
|
||||
"ms": "^2.1.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"range-parser": "^1.2.1",
|
||||
@ -898,74 +792,21 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz",
|
||||
"integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
||||
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"parseurl": "^1.3.3",
|
||||
"send": "^1.0.0"
|
||||
"send": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
@ -973,15 +814,69 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -1009,9 +904,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.26.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz",
|
||||
"integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==",
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
@ -1021,9 +916,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz",
|
||||
"integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
||||
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
@ -1043,15 +938,6 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "basic-node",
|
||||
"name": "openvidu-node",
|
||||
"version": "1.0.0",
|
||||
"description": "Basic server application built for Node.js with Express",
|
||||
"main": "index.js",
|
||||
"description": "OpenVidu server application built for Node.js with Express",
|
||||
"main": "src/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
"start": "node src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "16.4.5",
|
||||
"express": "5.0.1",
|
||||
"livekit-server-sdk": "^2.7.2"
|
||||
"dotenv": "16.5.0",
|
||||
"express": "5.1.0",
|
||||
"livekit-server-sdk": "2.13.0"
|
||||
}
|
||||
}
|
||||
|
||||
6
application-server/node/src/config.js
Normal file
6
application-server/node/src/config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export const SERVER_PORT = process.env.SERVER_PORT || 6080;
|
||||
|
||||
// LiveKit configuration
|
||||
export const LIVEKIT_URL = process.env.LIVEKIT_URL || "http://localhost:7880";
|
||||
export const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY || "devkey";
|
||||
export const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET || "secret";
|
||||
247
application-server/node/src/controllers/egress.controller.js
Normal file
247
application-server/node/src/controllers/egress.controller.js
Normal file
@ -0,0 +1,247 @@
|
||||
import e, { Router } from "express";
|
||||
import {
|
||||
DirectFileOutput,
|
||||
EgressClient,
|
||||
EncodedFileOutput,
|
||||
EncodedFileType,
|
||||
StreamOutput,
|
||||
StreamProtocol
|
||||
} from "livekit-server-sdk";
|
||||
import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET, LIVEKIT_URL } from "../config.js";
|
||||
|
||||
const egressClient = new EgressClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
|
||||
export const egressController = Router();
|
||||
|
||||
// Create a new RoomComposite egress
|
||||
egressController.post("/room-composite", async (req, res) => {
|
||||
const { roomName } = req.body;
|
||||
|
||||
if (!roomName) {
|
||||
res.status(400).json({ errorMessage: "'roomName' is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const outputs = {
|
||||
file: new EncodedFileOutput({
|
||||
fileType: EncodedFileType.MP4,
|
||||
filepath: "{room_name}-{room_id}-{time}"
|
||||
})
|
||||
};
|
||||
const options = {
|
||||
layout: "grid"
|
||||
};
|
||||
const egress = await egressClient.startRoomCompositeEgress(roomName, outputs, options);
|
||||
res.status(201).json({ egress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error creating RoomComposite egress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new RoomComposite egress to stream to a URL
|
||||
egressController.post("/stream", async (req, res) => {
|
||||
const { roomName, streamUrl } = req.body;
|
||||
|
||||
if (!roomName || !streamUrl) {
|
||||
res.status(400).json({ errorMessage: "'roomName' and 'streamUrl' are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const outputs = {
|
||||
stream: new StreamOutput({
|
||||
protocol: StreamProtocol.RTMP,
|
||||
urls: [streamUrl]
|
||||
})
|
||||
};
|
||||
const egress = await egressClient.startRoomCompositeEgress(roomName, outputs);
|
||||
res.status(201).json({ egress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error creating RoomComposite egress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new Participant egress
|
||||
egressController.post("/participant", async (req, res) => {
|
||||
const { roomName, participantIdentity } = req.body;
|
||||
|
||||
if (!roomName || !participantIdentity) {
|
||||
res.status(400).json({ errorMessage: "'roomName' and 'participantIdentity' are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const outputs = {
|
||||
file: new EncodedFileOutput({
|
||||
fileType: EncodedFileType.MP4,
|
||||
filepath: "{room_name}-{room_id}-{publisher_identity}-{time}"
|
||||
})
|
||||
};
|
||||
const options = {
|
||||
screenShare: false
|
||||
};
|
||||
const egress = await egressClient.startParticipantEgress(roomName, participantIdentity, outputs, options);
|
||||
res.status(201).json({ egress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error creating Participant egress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new TrackComposite egress
|
||||
egressController.post("/track-composite", async (req, res) => {
|
||||
const { roomName, videoTrackId, audioTrackId } = req.body;
|
||||
|
||||
if (!roomName || !videoTrackId || !audioTrackId) {
|
||||
res.status(400).json({ errorMessage: "'roomName', 'videoTrackId' and 'audioTrackId' are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const outputs = {
|
||||
file: new EncodedFileOutput({
|
||||
fileType: EncodedFileType.MP4,
|
||||
filepath: "{room_name}-{room_id}-{publisher_identity}-{time}"
|
||||
})
|
||||
};
|
||||
const options = {
|
||||
videoTrackId,
|
||||
audioTrackId
|
||||
};
|
||||
const egress = await egressClient.startTrackCompositeEgress(roomName, outputs, options);
|
||||
res.status(201).json({ egress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error creating TrackComposite egress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new Track egress
|
||||
egressController.post("/track", async (req, res) => {
|
||||
const { roomName, trackId } = req.body;
|
||||
|
||||
if (!roomName || !trackId) {
|
||||
res.status(400).json({ errorMessage: "'roomName' and 'trackId' are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const output = new DirectFileOutput({
|
||||
filepath: "{room_name}-{room_id}-{publisher_identity}-{track_source}-{track_id}-{time}"
|
||||
});
|
||||
const egress = await egressClient.startTrackEgress(roomName, output, trackId);
|
||||
res.status(201).json({ egress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error creating Track egress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new Web egress
|
||||
egressController.post("/web", async (req, res) => {
|
||||
const { url } = req.body;
|
||||
|
||||
if (!url) {
|
||||
res.status(400).json({ errorMessage: "'url' is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const outputs = {
|
||||
file: new EncodedFileOutput({
|
||||
fileType: EncodedFileType.MP4,
|
||||
filepath: "{time}"
|
||||
})
|
||||
};
|
||||
const egress = await egressClient.startWebEgress(url, outputs);
|
||||
res.status(201).json({ egress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error creating Web egress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ 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
|
||||
egressController.get("/", async (req, res) => {
|
||||
const { egressId, roomName, active } = req.query;
|
||||
|
||||
try {
|
||||
const options = {
|
||||
egressId: egressId ? String(egressId) : undefined,
|
||||
roomName: roomName ? String(roomName) : undefined,
|
||||
active: active ? active === "true" : undefined
|
||||
};
|
||||
const egresses = await egressClient.listEgress(options);
|
||||
res.json({ egresses });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error listing egresses";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Update egress layout
|
||||
egressController.post("/:egressId/layout", async (req, res) => {
|
||||
const { egressId } = req.params;
|
||||
const { layout } = req.body;
|
||||
|
||||
if (!layout) {
|
||||
res.status(400).json({ errorMessage: "'layout' is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const egress = await egressClient.updateLayout(egressId, layout);
|
||||
res.json({ egress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error updating egress layout";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Add/remove stream URLs to an egress
|
||||
egressController.post("/:egressId/streams", async (req, res) => {
|
||||
const { egressId } = req.params;
|
||||
const { streamUrlsToAdd, streamUrlsToRemove } = req.body;
|
||||
|
||||
if (!Array.isArray(streamUrlsToAdd) || !Array.isArray(streamUrlsToRemove)) {
|
||||
res.status(400).json({ errorMessage: "'streamUrlsToAdd' and 'streamUrlsToRemove' are required and must be arrays" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const egress = await egressClient.updateStream(egressId, streamUrlsToAdd, streamUrlsToRemove);
|
||||
res.json({ egress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error updating egress streams";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Stop an egress
|
||||
egressController.delete("/:egressId", async (req, res) => {
|
||||
const { egressId } = req.params;
|
||||
|
||||
try {
|
||||
await egressClient.stopEgress(egressId);
|
||||
res.json({ message: "Egress stopped" });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error stopping egress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
138
application-server/node/src/controllers/ingress.controller.js
Normal file
138
application-server/node/src/controllers/ingress.controller.js
Normal file
@ -0,0 +1,138 @@
|
||||
import { Router } from "express";
|
||||
import { IngressClient, IngressInput } from "livekit-server-sdk";
|
||||
import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET, LIVEKIT_URL } from "../config.js";
|
||||
|
||||
const ingressClient = new IngressClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
|
||||
export const ingressController = Router();
|
||||
|
||||
// Create a new RTMP ingress
|
||||
ingressController.post("/rtmp", async (req, res) => {
|
||||
const { roomName, participantIdentity } = req.body;
|
||||
|
||||
if (!roomName || !participantIdentity) {
|
||||
res.status(400).json({ errorMessage: "'roomName' and 'participantIdentity' are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const ingressOptions = {
|
||||
name: "rtmp-ingress",
|
||||
roomName,
|
||||
participantIdentity
|
||||
};
|
||||
const ingress = await ingressClient.createIngress(IngressInput.RTMP_INPUT, ingressOptions);
|
||||
res.status(201).json({ ingress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error creating RTMP ingress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new WHIP ingress
|
||||
ingressController.post("/whip", async (req, res) => {
|
||||
const { roomName, participantIdentity } = req.body;
|
||||
|
||||
if (!roomName || !participantIdentity) {
|
||||
res.status(400).json({ errorMessage: "'roomName' and 'participantIdentity' are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const ingressOptions = {
|
||||
name: "whip-ingress",
|
||||
roomName,
|
||||
participantIdentity
|
||||
};
|
||||
const ingress = await ingressClient.createIngress(IngressInput.WHIP_INPUT, ingressOptions);
|
||||
res.status(201).json({ ingress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error creating WHIP ingress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new URL ingress
|
||||
ingressController.post("/url", async (req, res) => {
|
||||
const { roomName, participantIdentity, url } = req.body;
|
||||
|
||||
if (!roomName || !participantIdentity || !url) {
|
||||
res.status(400).json({ errorMessage: "'roomName', 'participantIdentity' and 'url' are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const ingressOptions = {
|
||||
name: "url-ingress",
|
||||
roomName,
|
||||
participantIdentity,
|
||||
url
|
||||
};
|
||||
const ingress = await ingressClient.createIngress(IngressInput.URL_INPUT, ingressOptions);
|
||||
res.status(201).json({ ingress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error creating URL ingress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ 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
|
||||
ingressController.get("/", async (req, res) => {
|
||||
const { ingressId, roomName } = req.query;
|
||||
|
||||
try {
|
||||
const options = {
|
||||
ingressId: ingressId ? String(ingressId) : undefined,
|
||||
roomName: roomName ? String(roomName) : undefined
|
||||
};
|
||||
const ingresses = await ingressClient.listIngress(options);
|
||||
res.json({ ingresses });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error listing ingresses";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Update ingress
|
||||
ingressController.patch("/:ingressId", async (req, res) => {
|
||||
const { ingressId } = req.params;
|
||||
const { roomName } = req.body;
|
||||
|
||||
if (!roomName) {
|
||||
res.status(400).json({ errorMessage: "'roomName' is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const options = {
|
||||
name: "updated-ingress",
|
||||
roomName
|
||||
};
|
||||
const ingress = await ingressClient.updateIngress(ingressId, options);
|
||||
res.json({ ingress });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error updating ingress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete ingress
|
||||
ingressController.delete("/:ingressId", async (req, res) => {
|
||||
const { ingressId } = req.params;
|
||||
|
||||
try {
|
||||
await ingressClient.deleteIngress(ingressId);
|
||||
res.json({ message: "Ingress deleted" });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error deleting ingress";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
230
application-server/node/src/controllers/room.controller.js
Normal file
230
application-server/node/src/controllers/room.controller.js
Normal file
@ -0,0 +1,230 @@
|
||||
import { Router } from "express";
|
||||
import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET, LIVEKIT_URL } from "../config.js";
|
||||
import { DataPacket_Kind, RoomServiceClient } from "livekit-server-sdk";
|
||||
|
||||
const roomClient = new RoomServiceClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
|
||||
export const roomController = Router();
|
||||
|
||||
// Create a new room
|
||||
roomController.post("/", async (req, res) => {
|
||||
const { roomName } = req.body;
|
||||
|
||||
if (!roomName) {
|
||||
res.status(400).json({ errorMessage: "'roomName' is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const roomOptions = {
|
||||
name: roomName
|
||||
};
|
||||
const room = await roomClient.createRoom(roomOptions);
|
||||
res.status(201).json({ room });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error creating room";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// List rooms. If a room name is provided, only that room is listed
|
||||
roomController.get("/", async (req, res) => {
|
||||
const { roomName } = req.query;
|
||||
|
||||
try {
|
||||
const roomNames = roomName ? [String(roomName)] : [];
|
||||
const rooms = await roomClient.listRooms(roomNames);
|
||||
res.json({ rooms });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error listing rooms";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Update room metadata
|
||||
roomController.post("/:roomName/metadata", async (req, res) => {
|
||||
const { roomName } = req.params;
|
||||
const { metadata } = req.body;
|
||||
|
||||
if (!metadata) {
|
||||
res.status(400).json({ errorMessage: "'metadata' is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const room = await roomClient.updateRoomMetadata(roomName, metadata);
|
||||
res.json({ room });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error updating room metadata";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Send data message to participants in a room
|
||||
roomController.post("/:roomName/send-data", async (req, res) => {
|
||||
const { roomName } = req.params;
|
||||
const { data: rawData } = req.body;
|
||||
|
||||
if (!rawData) {
|
||||
res.status(400).json({ errorMessage: "'data' is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(JSON.stringify(rawData));
|
||||
const options = {
|
||||
topic: "chat",
|
||||
destinationSids: [] // Send to all participants
|
||||
};
|
||||
await roomClient.sendData(roomName, data, DataPacket_Kind.RELIABLE, options);
|
||||
res.json({ message: "Data message sent" });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error sending data message";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete a room
|
||||
roomController.delete("/:roomName", async (req, res) => {
|
||||
const { roomName } = req.params;
|
||||
|
||||
try {
|
||||
await roomClient.deleteRoom(roomName);
|
||||
res.json({ message: "Room deleted" });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error deleting room";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// List participants in a room
|
||||
roomController.get("/:roomName/participants", async (req, res) => {
|
||||
const { roomName } = req.params;
|
||||
|
||||
try {
|
||||
const participants = await roomClient.listParticipants(roomName);
|
||||
res.json({ participants });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error listing participants";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Get a participant in a room
|
||||
roomController.get("/:roomName/participants/:participantIdentity", async (req, res) => {
|
||||
const { roomName, participantIdentity } = req.params;
|
||||
|
||||
try {
|
||||
const participant = await roomClient.getParticipant(roomName, participantIdentity);
|
||||
res.json({ participant });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error getting participant";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Update a participant in a room
|
||||
roomController.patch("/:roomName/participants/:participantIdentity", async (req, res) => {
|
||||
const { roomName, participantIdentity } = req.params;
|
||||
const { metadata } = req.body;
|
||||
|
||||
try {
|
||||
const updateParticipantOptions = {
|
||||
metadata,
|
||||
permission: {
|
||||
canPublish: false,
|
||||
canSubscribe: true
|
||||
}
|
||||
};
|
||||
const participant = await roomClient.updateParticipant(roomName, participantIdentity, updateParticipantOptions);
|
||||
res.json({ participant });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error updating participant";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Remove a participant from a room
|
||||
roomController.delete("/:roomName/participants/:participantIdentity", async (req, res) => {
|
||||
const { roomName, participantIdentity } = req.params;
|
||||
|
||||
try {
|
||||
await roomClient.removeParticipant(roomName, participantIdentity);
|
||||
res.json({ message: "Participant removed" });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error removing participant";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Mute published track of a participant in a room
|
||||
roomController.post("/:roomName/participants/:participantIdentity/mute", async (req, res) => {
|
||||
const { roomName, participantIdentity } = req.params;
|
||||
const { trackId } = req.body;
|
||||
|
||||
if (!trackId) {
|
||||
res.status(400).json({ errorMessage: "'trackId' is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const track = await roomClient.mutePublishedTrack(roomName, participantIdentity, trackId, true);
|
||||
res.json({ track });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error muting track";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe participant to tracks in a room
|
||||
roomController.post("/:roomName/participants/:participantIdentity/subscribe", async (req, res) => {
|
||||
const { roomName, participantIdentity } = req.params;
|
||||
const { trackIds } = req.body;
|
||||
|
||||
if (!Array.isArray(trackIds)) {
|
||||
res.status(400).json({ errorMessage: "'trackIds' is required and must be an array" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await roomClient.updateSubscriptions(roomName, participantIdentity, trackIds, true);
|
||||
const message = "Participant subscribed to tracks";
|
||||
res.json({ message });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error subscribing participant to tracks";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
|
||||
// Unsubscribe participant from tracks in a room
|
||||
roomController.post("/:roomName/participants/:participantIdentity/unsubscribe", async (req, res) => {
|
||||
const { roomName, participantIdentity } = req.params;
|
||||
const { trackIds } = req.body;
|
||||
|
||||
if (!Array.isArray(trackIds)) {
|
||||
res.status(400).json({ errorMessage: "'trackIds' is required and must be an array" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await roomClient.updateSubscriptions(roomName, participantIdentity, trackIds, false);
|
||||
const message = "Participant unsubscribed from tracks";
|
||||
res.json({ message });
|
||||
} catch (error) {
|
||||
const errorMessage = "Error unsubscribing participant from tracks";
|
||||
console.error(errorMessage, error);
|
||||
res.status(500).json({ errorMessage });
|
||||
}
|
||||
});
|
||||
29
application-server/node/src/controllers/token.controller.js
Normal file
29
application-server/node/src/controllers/token.controller.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { Router } from "express";
|
||||
import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET } from "../config.js";
|
||||
import { AccessToken } from "livekit-server-sdk";
|
||||
|
||||
export const tokenController = Router();
|
||||
|
||||
tokenController.post("/", async (req, res) => {
|
||||
const { roomName, participantName } = req.body;
|
||||
|
||||
if (!roomName || !participantName) {
|
||||
res.status(400).json({ errorMessage: "'roomName' and 'participantName' are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, {
|
||||
identity: participantName
|
||||
});
|
||||
|
||||
const grant = {
|
||||
roomJoin: true,
|
||||
room: roomName,
|
||||
canPublish: true,
|
||||
canSubscribe: true
|
||||
};
|
||||
at.addGrant(grant);
|
||||
|
||||
const token = await at.toJwt();
|
||||
res.json({ token });
|
||||
});
|
||||
@ -0,0 +1,19 @@
|
||||
import express, { Router } from "express";
|
||||
import { WebhookReceiver } from "livekit-server-sdk";
|
||||
import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET } from "../config.js";
|
||||
|
||||
const webhookReceiver = new WebhookReceiver(LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
|
||||
export const webhookController = Router();
|
||||
webhookController.use(express.raw({ type: "application/webhook+json" }));
|
||||
|
||||
webhookController.post("/", async (req, res) => {
|
||||
try {
|
||||
const webhookEvent = await webhookReceiver.receive(req.body, req.get("Authorization"));
|
||||
console.log(webhookEvent);
|
||||
} catch (error) {
|
||||
console.error("Error validating webhook event", error);
|
||||
}
|
||||
|
||||
res.status(200).send();
|
||||
});
|
||||
24
application-server/node/src/index.js
Normal file
24
application-server/node/src/index.js
Normal file
@ -0,0 +1,24 @@
|
||||
import "dotenv/config.js";
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import { SERVER_PORT } from "./config.js";
|
||||
import { tokenController } from "./controllers/token.controller.js";
|
||||
import { webhookController } from "./controllers/webhook.controller.js";
|
||||
import { roomController } from "./controllers/room.controller.js";
|
||||
import { egressController } from "./controllers/egress.controller.js";
|
||||
import { ingressController } from "./controllers/ingress.controller.js";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
app.use("/token", tokenController);
|
||||
app.use("/livekit/webhook", webhookController);
|
||||
app.use("/rooms", roomController);
|
||||
app.use("/egresses", egressController);
|
||||
app.use("/ingresses", ingressController);
|
||||
|
||||
app.listen(SERVER_PORT, () => {
|
||||
console.log("Server started on port:", SERVER_PORT);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user