Skip to content

Commit a27015e

Browse files
authored
[RTC-435] Add track, peer metadata and track metadata (#27)
* Add tracks * WIP update api * Lint * Add examples * Improve docs of server notifications * endlines * Format * Update proto * Update client
1 parent 52da564 commit a27015e

30 files changed

+820
-33
lines changed

docs/server_notifications.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Server Notifications
2+
3+
The Jellyfish can send one of the following notifications:
4+
5+
`ServerMessageRoomCreated`,
6+
`ServerMessageRoomDeleted`,
7+
`ServerMessageRoomCrashed`,
8+
`ServerMessagePeerConnected`,
9+
`ServerMessagePeerDisconnected`,
10+
`ServerMessagePeerCrashed`,
11+
`ServerMessageComponentCrashed`,
12+
`ServerMessageTrackAdded`,
13+
`ServerMessageTrackMetadataUpdated`,
14+
`ServerMessageTrackRemoved`,
15+
`ServerMessageHlsPlayable`,
16+
`ServerMessageMetricsReport`

examples/room_api.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from jellyfish import ComponentOptionsHLS, PeerOptionsWebRTC, RoomApi
2+
3+
# Create a room
4+
room_api = RoomApi(server_address="localhost:5002", server_api_token="development")
5+
6+
jellyfish_address, room = room_api.create_room(
7+
video_codec="h264", webhook_url="http://localhost:5000/webhook"
8+
)
9+
print((jellyfish_address, room))
10+
11+
# Add peer to the room
12+
peer_token, peer_webrtc = room_api.add_peer(room.id, options=PeerOptionsWebRTC())
13+
print((peer_token, peer_webrtc))
14+
15+
# Add component to the room
16+
component_hls = room_api.add_component(room.id, options=ComponentOptionsHLS())
17+
print(component_hls)

examples/server_notifications.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import asyncio
2+
3+
from jellyfish import Notifier, RoomApi
4+
from jellyfish.events import ServerMessageTrackAdded, ServerMessageTrackType
5+
6+
notifier = Notifier(server_address="localhost:5002", server_api_token="development")
7+
8+
9+
@notifier.on_server_notification
10+
def handle_notification(server_notification):
11+
print(f"Received a notification: {server_notification}")
12+
13+
if isinstance(server_notification, ServerMessageTrackAdded):
14+
if server_notification.track.type == ServerMessageTrackType.TRACK_TYPE_AUDIO:
15+
print("New audio track has been added")
16+
elif server_notification.track.type == ServerMessageTrackType.TRACK_TYPE_VIDEO:
17+
print("New video track has been added")
18+
19+
20+
@notifier.on_metrics
21+
def handle_metrics(metrics_report):
22+
print(f"Received WebRTC metrics: {metrics_report}")
23+
24+
25+
async def test_notifier():
26+
notifier_task = asyncio.create_task(notifier.connect())
27+
28+
# Wait for notifier to be ready to receive messages
29+
await notifier.wait_ready()
30+
31+
# Create a room to trigger a server notification
32+
room_api = RoomApi()
33+
room_api.create_room()
34+
35+
await notifier_task
36+
37+
38+
asyncio.run(test_notifier())

jellyfish/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
)
3030

3131
# API
32-
from jellyfish._webhook_notifier import receive_json
32+
from jellyfish._webhook_notifier import receive_binary
3333
from jellyfish._ws_notifier import Notifier
3434
from jellyfish.api._recording_api import RecordingApi
3535
from jellyfish.api._room_api import RoomApi
@@ -38,7 +38,7 @@
3838
"RoomApi",
3939
"RecordingApi",
4040
"Notifier",
41-
"receive_json",
41+
"receive_binary",
4242
"Room",
4343
"RoomConfig",
4444
"RoomConfigVideoCodec",

jellyfish/_openapi_client/api/default/__init__.py

Whitespace-only changes.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
from http import HTTPStatus
2+
from typing import Any, Dict, Optional, Union
3+
4+
import httpx
5+
6+
from ... import errors
7+
from ...client import AuthenticatedClient, Client
8+
from ...models.healthcheck_response import HealthcheckResponse
9+
from ...types import Response
10+
11+
12+
def _get_kwargs() -> Dict[str, Any]:
13+
return {
14+
"method": "get",
15+
"url": "/health",
16+
}
17+
18+
19+
def _parse_response(
20+
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
21+
) -> Optional[HealthcheckResponse]:
22+
if response.status_code == HTTPStatus.OK:
23+
response_200 = HealthcheckResponse.from_dict(response.json())
24+
25+
return response_200
26+
if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR:
27+
response_500 = HealthcheckResponse.from_dict(response.json())
28+
29+
return response_500
30+
if client.raise_on_unexpected_status:
31+
raise errors.UnexpectedStatus(response.status_code, response.content)
32+
else:
33+
return None
34+
35+
36+
def _build_response(
37+
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
38+
) -> Response[HealthcheckResponse]:
39+
return Response(
40+
status_code=HTTPStatus(response.status_code),
41+
content=response.content,
42+
headers=response.headers,
43+
parsed=_parse_response(client=client, response=response),
44+
)
45+
46+
47+
def sync_detailed(
48+
*,
49+
client: Union[AuthenticatedClient, Client],
50+
) -> Response[HealthcheckResponse]:
51+
"""Describes the health of Jellyfish
52+
53+
Raises:
54+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
55+
httpx.TimeoutException: If the request takes longer than Client.timeout.
56+
57+
Returns:
58+
Response[HealthcheckResponse]
59+
"""
60+
61+
kwargs = _get_kwargs()
62+
63+
response = client.get_httpx_client().request(
64+
**kwargs,
65+
)
66+
67+
return _build_response(client=client, response=response)
68+
69+
70+
def sync(
71+
*,
72+
client: Union[AuthenticatedClient, Client],
73+
) -> Optional[HealthcheckResponse]:
74+
"""Describes the health of Jellyfish
75+
76+
Raises:
77+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
78+
httpx.TimeoutException: If the request takes longer than Client.timeout.
79+
80+
Returns:
81+
HealthcheckResponse
82+
"""
83+
84+
return sync_detailed(
85+
client=client,
86+
).parsed
87+
88+
89+
async def asyncio_detailed(
90+
*,
91+
client: Union[AuthenticatedClient, Client],
92+
) -> Response[HealthcheckResponse]:
93+
"""Describes the health of Jellyfish
94+
95+
Raises:
96+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
97+
httpx.TimeoutException: If the request takes longer than Client.timeout.
98+
99+
Returns:
100+
Response[HealthcheckResponse]
101+
"""
102+
103+
kwargs = _get_kwargs()
104+
105+
response = await client.get_async_httpx_client().request(**kwargs)
106+
107+
return _build_response(client=client, response=response)
108+
109+
110+
async def asyncio(
111+
*,
112+
client: Union[AuthenticatedClient, Client],
113+
) -> Optional[HealthcheckResponse]:
114+
"""Describes the health of Jellyfish
115+
116+
Raises:
117+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
118+
httpx.TimeoutException: If the request takes longer than Client.timeout.
119+
120+
Returns:
121+
HealthcheckResponse
122+
"""
123+
124+
return (
125+
await asyncio_detailed(
126+
client=client,
127+
)
128+
).parsed

jellyfish/_openapi_client/models/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
from .component_properties_rtsp import ComponentPropertiesRTSP
1616
from .component_rtsp import ComponentRTSP
1717
from .error import Error
18+
from .health_report import HealthReport
19+
from .health_report_distribution import HealthReportDistribution
20+
from .health_report_status import HealthReportStatus
21+
from .healthcheck_response import HealthcheckResponse
1822
from .peer import Peer
1923
from .peer_details_response import PeerDetailsResponse
2024
from .peer_details_response_data import PeerDetailsResponseData
@@ -30,6 +34,8 @@
3034
from .rooms_listing_response import RoomsListingResponse
3135
from .s3_credentials import S3Credentials
3236
from .subscription_config import SubscriptionConfig
37+
from .track import Track
38+
from .track_type import TrackType
3339

3440
__all__ = (
3541
"AddComponentJsonBody",
@@ -47,6 +53,10 @@
4753
"ComponentPropertiesRTSP",
4854
"ComponentRTSP",
4955
"Error",
56+
"HealthcheckResponse",
57+
"HealthReport",
58+
"HealthReportDistribution",
59+
"HealthReportStatus",
5060
"Peer",
5161
"PeerDetailsResponse",
5262
"PeerDetailsResponseData",
@@ -62,4 +72,6 @@
6272
"RoomsListingResponse",
6373
"S3Credentials",
6474
"SubscriptionConfig",
75+
"Track",
76+
"TrackType",
6577
)

jellyfish/_openapi_client/models/component_file.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
if TYPE_CHECKING:
99
from ..models.component_properties_file import ComponentPropertiesFile
10+
from ..models.track import Track
1011

1112

1213
T = TypeVar("T", bound="ComponentFile")
@@ -18,6 +19,8 @@ class ComponentFile:
1819

1920
id: str
2021
"""Assigned component ID"""
22+
tracks: List["Track"]
23+
"""List of all component's tracks"""
2124
type: str
2225
"""Component type"""
2326
properties: Union[Unset, "ComponentPropertiesFile"] = UNSET
@@ -28,6 +31,12 @@ class ComponentFile:
2831
def to_dict(self) -> Dict[str, Any]:
2932
"""@private"""
3033
id = self.id
34+
tracks = []
35+
for tracks_item_data in self.tracks:
36+
tracks_item = tracks_item_data.to_dict()
37+
38+
tracks.append(tracks_item)
39+
3140
type = self.type
3241
properties: Union[Unset, Dict[str, Any]] = UNSET
3342
if not isinstance(self.properties, Unset):
@@ -38,6 +47,7 @@ def to_dict(self) -> Dict[str, Any]:
3847
field_dict.update(
3948
{
4049
"id": id,
50+
"tracks": tracks,
4151
"type": type,
4252
}
4353
)
@@ -50,10 +60,18 @@ def to_dict(self) -> Dict[str, Any]:
5060
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
5161
"""@private"""
5262
from ..models.component_properties_file import ComponentPropertiesFile
63+
from ..models.track import Track
5364

5465
d = src_dict.copy()
5566
id = d.pop("id")
5667

68+
tracks = []
69+
_tracks = d.pop("tracks")
70+
for tracks_item_data in _tracks:
71+
tracks_item = Track.from_dict(tracks_item_data)
72+
73+
tracks.append(tracks_item)
74+
5775
type = d.pop("type")
5876

5977
_properties = d.pop("properties", UNSET)
@@ -65,6 +83,7 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
6583

6684
component_file = cls(
6785
id=id,
86+
tracks=tracks,
6887
type=type,
6988
properties=properties,
7089
)

jellyfish/_openapi_client/models/component_hls.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
if TYPE_CHECKING:
77
from ..models.component_properties_hls import ComponentPropertiesHLS
8+
from ..models.track import Track
89

910

1011
T = TypeVar("T", bound="ComponentHLS")
@@ -18,6 +19,8 @@ class ComponentHLS:
1819
"""Assigned component ID"""
1920
properties: "ComponentPropertiesHLS"
2021
"""Properties specific to the HLS component"""
22+
tracks: List["Track"]
23+
"""List of all component's tracks"""
2124
type: str
2225
"""Component type"""
2326
additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict)
@@ -28,6 +31,12 @@ def to_dict(self) -> Dict[str, Any]:
2831
id = self.id
2932
properties = self.properties.to_dict()
3033

34+
tracks = []
35+
for tracks_item_data in self.tracks:
36+
tracks_item = tracks_item_data.to_dict()
37+
38+
tracks.append(tracks_item)
39+
3140
type = self.type
3241

3342
field_dict: Dict[str, Any] = {}
@@ -36,6 +45,7 @@ def to_dict(self) -> Dict[str, Any]:
3645
{
3746
"id": id,
3847
"properties": properties,
48+
"tracks": tracks,
3949
"type": type,
4050
}
4151
)
@@ -46,17 +56,26 @@ def to_dict(self) -> Dict[str, Any]:
4656
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
4757
"""@private"""
4858
from ..models.component_properties_hls import ComponentPropertiesHLS
59+
from ..models.track import Track
4960

5061
d = src_dict.copy()
5162
id = d.pop("id")
5263

5364
properties = ComponentPropertiesHLS.from_dict(d.pop("properties"))
5465

66+
tracks = []
67+
_tracks = d.pop("tracks")
68+
for tracks_item_data in _tracks:
69+
tracks_item = Track.from_dict(tracks_item_data)
70+
71+
tracks.append(tracks_item)
72+
5573
type = d.pop("type")
5674

5775
component_hls = cls(
5876
id=id,
5977
properties=properties,
78+
tracks=tracks,
6079
type=type,
6180
)
6281

0 commit comments

Comments
 (0)