Jellyfish Python Server SDK
Python server SDK for the Jellyfish Media Server.
Read the docs here
Installation
pip install jellyfish-server-sdk
Usage
The SDK exports two main classes for interacting with Jellyfish server:
RoomApi
and Notifier
.
RoomApi
wraps http REST api calls, while Notifier
is responsible for receiving real-time updates from the server.
RoomApi
Create a RoomApi
instance, providing the jellyfish server address and api token
from jellyfish import RoomApi
room_api = RoomApi(server_address="localhost:5002", server_api_token="development")
You can use it to interact with Jellyfish, manage rooms, peers and components
# Create a room
jellyfish_address, room = room_api.create_room(video_codec="h264", webhook_url="http://localhost:5000/webhook")
# '127.0.0.1:5002', Room(components=[], config=RoomConfig(max_peers=None, video_codec=<RoomConfigVideoCodec.H264: 'h264'>, webhook_url='http://localhost:5000/webhook'), id='1d905478-ccfc-44d6-a6e7-8ccb1b38d955', peers=[])
# Add peer to the room
from jellyfish import PeerOptionsWebRTC
peer_token, peer_webrtc = room_api.add_peer(room.id, options=PeerOptionsWebRTC())
# 'M8TUGhj-L11KpyG-2zBPIo', Peer(id='b1232c7e-c969-4450-acdf-ea24f3cdd7f6', status=<PeerStatus.DISCONNECTED: 'disconnected'>, type='webrtc')
# Add component to the room
from jellyfish import ComponentOptionsHLS
component_hls = room_api.add_component(room.id, options=ComponentOptionsHLS())
# ComponentHLS(id='5f062447-a9f7-45ed-8d1b-511f77dc78ae', properties=ComponentPropertiesHLS(low_latency=False, persistent=False, playable=False, subscribe_mode=<ComponentPropertiesHLSSubscribeMode.AUTO: 'auto'>, target_window_duration=None), type='hls')
All methods in RoomApi
may raise one of the exceptions deriving from jellyfish.errors.HTTPError
. They are defined in jellyfish.errors
.
Notifier
Notifier allows for receiving real-time updates from the Jellyfish Server.
You can read more about notifications in the Jellyfish Docs.
Create Notifier
instance
from jellyfish import Notifier
notifier = Notifier(server_address='localhost:5002', server_api_token='development')
Then define handlers for incoming messages
@notifier.on_server_notification
def handle_notification(server_notification):
print(f'Received a notification: {server_notification}')
@notifier.on_metrics
def handle_metrics(metrics_report):
print(f'Received WebRTC metrics: {metrics_report}')
After that you can start the notifier
async def test_notifier():
notifier_task = asyncio.create_task(notifier.connect())
# Wait for notifier to be ready to receive messages
await notifier.wait_ready()
# Create a room to trigger a server notification
room_api = RoomApi()
room_api.create_room()
await notifier_task
asyncio.run(test_notifier())
# Received a notification: ServerMessageRoomCreated(room_id='69a3fd1a-6a4d-47bc-ae54-0c72b0d05e29')
# Received WebRTC metrics: ServerMessageMetricsReport(metrics='{}')
Cluster of Jellyfishes
The cluster of jellyfishes has got embedded load balancer, which means that a new room will be created on jellyfish with the least usage. At the moment to modify this specific room you must communicate with the jellyfish on which this room was created.
room_api = RoomApi(server_address='localhost:5002')
# Create a room to trigger a server notification with h264 as a codec,
# that allow to use HLS.
address, room = room_api.create_room(video_codec="h264")
# Create new room api with returned jellyfish address as a room could be
# created on a different jellyfish instance
# (if you communicate with a cluster of jellyfishes)
new_room_api = RoomApi(server_address=address)
# Add HLS component with manual subscribe mode, we use here `new_room_api` as we are sure that this API refers to the jellyfish on which this room was created.
_hls_component = new_room_api.add_component(
room.id,
ComponentOptionsHLS(subscribe_mode=ComponentOptionsHLSSubscribeMode.MANUAL),
)
Testing
You can test the SDK by running
poetry run ci_test
In local development you can use
poetry run local_test
Format & Lint
You can format code by running
poetry run format
You can check linter by running
poetry run lint
Copyright and License
Copyright 2023, Software Mansion
Licensed under the Apache License, Version 2.0
1""" 2 .. include:: ../README.md 3""" 4 5# pylint: disable=locally-disabled, no-name-in-module, import-error 6 7# Exceptions and Server Messages 8from jellyfish import errors, events 9 10# Models 11from jellyfish._openapi_client.models import ( 12 ComponentFile, 13 ComponentHLS, 14 ComponentOptionsFile, 15 ComponentOptionsHLS, 16 ComponentOptionsHLSSubscribeMode, 17 ComponentOptionsRecording, 18 ComponentOptionsRecordingSubscribeMode, 19 ComponentOptionsRTSP, 20 ComponentOptionsSIP, 21 ComponentPropertiesFile, 22 ComponentPropertiesHLS, 23 ComponentPropertiesHLSSubscribeMode, 24 ComponentPropertiesRecording, 25 ComponentPropertiesRecordingSubscribeMode, 26 ComponentPropertiesRTSP, 27 ComponentPropertiesSIP, 28 ComponentPropertiesSIPSIPCredentials, 29 ComponentRecording, 30 ComponentRTSP, 31 ComponentSIP, 32 Peer, 33 PeerOptionsWebRTC, 34 PeerStatus, 35 Room, 36 RoomConfig, 37 RoomConfigVideoCodec, 38 S3Credentials, 39 SIPCredentials, 40) 41 42# API 43from jellyfish._webhook_notifier import receive_binary 44from jellyfish._ws_notifier import Notifier 45from jellyfish.api._recording_api import RecordingApi 46from jellyfish.api._room_api import RoomApi 47 48__all__ = [ 49 "RoomApi", 50 "RecordingApi", 51 "Notifier", 52 "receive_binary", 53 "Room", 54 "RoomConfig", 55 "RoomConfigVideoCodec", 56 "Peer", 57 "PeerOptionsWebRTC", 58 "PeerStatus", 59 "ComponentHLS", 60 "ComponentOptionsHLS", 61 "ComponentOptionsHLSSubscribeMode", 62 "ComponentPropertiesHLS", 63 "ComponentPropertiesHLSSubscribeMode", 64 "ComponentSIP", 65 "ComponentOptionsSIP", 66 "ComponentPropertiesSIP", 67 "ComponentPropertiesSIPSIPCredentials", 68 "ComponentFile", 69 "ComponentRTSP", 70 "ComponentOptionsRTSP", 71 "ComponentPropertiesRTSP", 72 "ComponentFile", 73 "ComponentOptionsFile", 74 "ComponentPropertiesFile", 75 "events", 76 "errors", 77 "SIPCredentials", 78 "ComponentRecording", 79 "ComponentOptionsRecording", 80 "ComponentOptionsRecordingSubscribeMode", 81 "ComponentPropertiesRecording", 82 "ComponentPropertiesRecordingSubscribeMode", 83 "S3Credentials", 84] 85__docformat__ = "restructuredtext"
43class RoomApi(BaseApi): 44 """Allows for managing rooms""" 45 46 def __init__( 47 self, 48 server_address: str = "localhost:5002", 49 server_api_token: str = "development", 50 secure: bool = False, 51 ): 52 """ 53 Create RoomApi instance, providing the jellyfish address and api token. 54 Set secure to `True` for `https` and `False` for `http` connection (default). 55 """ 56 super().__init__( 57 server_address=server_address, 58 server_api_token=server_api_token, 59 secure=secure, 60 ) 61 62 def create_room( 63 self, 64 room_id: str = None, 65 max_peers: int = None, 66 video_codec: Literal["h264", "vp8"] = None, 67 webhook_url: str = None, 68 peerless_purge_timeout: int = None, 69 peer_disconnected_timeout: int = None, 70 ) -> Tuple[str, Room]: 71 """ 72 Creates a new room 73 74 Returns a tuple (`jellyfish_address`, `Room`) - the address of the Jellyfish 75 in which the room has been created and the created `Room` 76 77 The returned address may be different from the current `RoomApi` instance. 78 In such case, a new `RoomApi` instance has to be created using 79 the returned address in order to interact with the room. 80 """ 81 82 if video_codec is not None: 83 video_codec = RoomConfigVideoCodec(video_codec) 84 else: 85 video_codec = None 86 87 room_config = RoomConfig( 88 room_id=room_id, 89 max_peers=max_peers, 90 video_codec=video_codec, 91 webhook_url=webhook_url, 92 peerless_purge_timeout=peerless_purge_timeout, 93 peer_disconnected_timeout=peer_disconnected_timeout, 94 ) 95 96 resp = self._request(room_create_room, json_body=room_config) 97 return (resp.data.jellyfish_address, resp.data.room) 98 99 def delete_room(self, room_id: str) -> None: 100 """Deletes a room""" 101 102 return self._request(room_delete_room, room_id=room_id) 103 104 def get_all_rooms(self) -> list: 105 """Returns list of all rooms""" 106 107 return self._request(room_get_all_rooms).data 108 109 def get_room(self, room_id: str) -> Room: 110 """Returns room with the given id""" 111 112 return self._request(room_get_room, room_id=room_id).data 113 114 def add_peer( 115 self, room_id: str, options: PeerOptionsWebRTC 116 ) -> PeerDetailsResponseData: 117 """ 118 Creates peer in the room 119 120 Currently only `webrtc` peer is supported 121 122 Returns a tuple (`peer_token`, `Peer`) - the token needed by Peer 123 to authenticate to Jellyfish and the new `Peer`. 124 125 The possible options to pass for peer are `PeerOptionsWebRTC`. 126 """ 127 128 peer_type = "webrtc" 129 json_body = AddPeerJsonBody(type=peer_type, options=options) 130 131 resp = self._request(room_add_peer, room_id=room_id, json_body=json_body) 132 return PeerDetailsResponseData( 133 peer=resp.data.peer, 134 token=resp.data.token, 135 peer_websocket_url=resp.data.peer_websocket_url, 136 ) 137 138 def delete_peer(self, room_id: str, peer_id: str) -> None: 139 """Deletes peer""" 140 141 return self._request(room_delete_peer, id=peer_id, room_id=room_id) 142 143 def add_component( 144 self, 145 room_id: str, 146 options: Union[ 147 ComponentOptionsFile, 148 ComponentOptionsHLS, 149 ComponentOptionsRecording, 150 ComponentOptionsRTSP, 151 ComponentOptionsSIP, 152 ], 153 ) -> Union[ 154 ComponentFile, ComponentHLS, ComponentRecording, ComponentRTSP, ComponentSIP 155 ]: 156 """ 157 Creates component in the room. 158 Currently there are 4 different components: 159 * File Component for which the options are `ComponentOptionsFile` 160 * HLS Component which options are `ComponentOptionsHLS` 161 * Recording Component which options are `ComponentOptionsRecording` 162 * RTSP Component which options are `ComponentOptionsRTSP` 163 * SIP Component which options are `ComponentOptionsSIP` 164 """ 165 166 if isinstance(options, ComponentOptionsFile): 167 component_type = "file" 168 elif isinstance(options, ComponentOptionsHLS): 169 component_type = "hls" 170 elif isinstance(options, ComponentOptionsRecording): 171 component_type = "recording" 172 elif isinstance(options, ComponentOptionsRTSP): 173 component_type = "rtsp" 174 elif isinstance(options, ComponentOptionsSIP): 175 component_type = "sip" 176 else: 177 raise ValueError( 178 "options must be ComponentOptionsFile, ComponentOptionsHLS," 179 "ComponentOptionsRTSP, ComponentOptionsRecording or ComponentOptionsSIP" 180 ) 181 182 json_body = AddComponentJsonBody(type=component_type, options=options) 183 184 return self._request( 185 room_add_component, room_id=room_id, json_body=json_body 186 ).data 187 188 def delete_component(self, room_id: str, component_id: str) -> None: 189 """Deletes component""" 190 191 return self._request(room_delete_component, id=component_id, room_id=room_id) 192 193 def subscribe(self, room_id: str, component_id: str, origins: List[str]): 194 """ 195 In order to subscribe the component to peers/components, 196 the component should be initialized with the subscribe_mode set to manual. 197 This mode proves beneficial when you do not wish to record or stream 198 all the available streams within a room. 199 It allows for selective addition instead – 200 you can manually select specific streams. 201 For instance, you could opt to record only the stream of an event's host. 202 """ 203 204 return self._request( 205 subscribe_to, 206 room_id=room_id, 207 component_id=component_id, 208 json_body=SubscriptionConfig(origins=origins), 209 ) 210 211 def sip_dial(self, room_id: str, component_id: str, phone_number: str): 212 """ 213 Starts a phone call from a specified component to a provided phone number. 214 215 This is asynchronous operation. 216 In case of providing incorrect phone number you will receive 217 notification `ComponentCrashed`. 218 """ 219 220 return self._request( 221 sip_dial, 222 room_id=room_id, 223 component_id=component_id, 224 json_body=DialConfig(phone_number=phone_number), 225 ) 226 227 def sip_end_call(self, room_id: str, component_id: str): 228 """ 229 End a phone call on a specified SIP component. 230 231 This is asynchronous operation. 232 """ 233 234 return self._request( 235 sip_end_call, 236 room_id=room_id, 237 component_id=component_id, 238 )
Allows for managing rooms
46 def __init__( 47 self, 48 server_address: str = "localhost:5002", 49 server_api_token: str = "development", 50 secure: bool = False, 51 ): 52 """ 53 Create RoomApi instance, providing the jellyfish address and api token. 54 Set secure to `True` for `https` and `False` for `http` connection (default). 55 """ 56 super().__init__( 57 server_address=server_address, 58 server_api_token=server_api_token, 59 secure=secure, 60 )
Create RoomApi instance, providing the jellyfish address and api token.
Set secure to True
for https
and False
for http
connection (default).
62 def create_room( 63 self, 64 room_id: str = None, 65 max_peers: int = None, 66 video_codec: Literal["h264", "vp8"] = None, 67 webhook_url: str = None, 68 peerless_purge_timeout: int = None, 69 peer_disconnected_timeout: int = None, 70 ) -> Tuple[str, Room]: 71 """ 72 Creates a new room 73 74 Returns a tuple (`jellyfish_address`, `Room`) - the address of the Jellyfish 75 in which the room has been created and the created `Room` 76 77 The returned address may be different from the current `RoomApi` instance. 78 In such case, a new `RoomApi` instance has to be created using 79 the returned address in order to interact with the room. 80 """ 81 82 if video_codec is not None: 83 video_codec = RoomConfigVideoCodec(video_codec) 84 else: 85 video_codec = None 86 87 room_config = RoomConfig( 88 room_id=room_id, 89 max_peers=max_peers, 90 video_codec=video_codec, 91 webhook_url=webhook_url, 92 peerless_purge_timeout=peerless_purge_timeout, 93 peer_disconnected_timeout=peer_disconnected_timeout, 94 ) 95 96 resp = self._request(room_create_room, json_body=room_config) 97 return (resp.data.jellyfish_address, resp.data.room)
Creates a new room
Returns a tuple (jellyfish_address
, Room
) - the address of the Jellyfish
in which the room has been created and the created Room
The returned address may be different from the current RoomApi
instance.
In such case, a new RoomApi
instance has to be created using
the returned address in order to interact with the room.
99 def delete_room(self, room_id: str) -> None: 100 """Deletes a room""" 101 102 return self._request(room_delete_room, room_id=room_id)
Deletes a room
104 def get_all_rooms(self) -> list: 105 """Returns list of all rooms""" 106 107 return self._request(room_get_all_rooms).data
Returns list of all rooms
109 def get_room(self, room_id: str) -> Room: 110 """Returns room with the given id""" 111 112 return self._request(room_get_room, room_id=room_id).data
Returns room with the given id
114 def add_peer( 115 self, room_id: str, options: PeerOptionsWebRTC 116 ) -> PeerDetailsResponseData: 117 """ 118 Creates peer in the room 119 120 Currently only `webrtc` peer is supported 121 122 Returns a tuple (`peer_token`, `Peer`) - the token needed by Peer 123 to authenticate to Jellyfish and the new `Peer`. 124 125 The possible options to pass for peer are `PeerOptionsWebRTC`. 126 """ 127 128 peer_type = "webrtc" 129 json_body = AddPeerJsonBody(type=peer_type, options=options) 130 131 resp = self._request(room_add_peer, room_id=room_id, json_body=json_body) 132 return PeerDetailsResponseData( 133 peer=resp.data.peer, 134 token=resp.data.token, 135 peer_websocket_url=resp.data.peer_websocket_url, 136 )
Creates peer in the room
Currently only webrtc
peer is supported
Returns a tuple (peer_token
, Peer
) - the token needed by Peer
to authenticate to Jellyfish and the new Peer
.
The possible options to pass for peer are PeerOptionsWebRTC
.
138 def delete_peer(self, room_id: str, peer_id: str) -> None: 139 """Deletes peer""" 140 141 return self._request(room_delete_peer, id=peer_id, room_id=room_id)
Deletes peer
143 def add_component( 144 self, 145 room_id: str, 146 options: Union[ 147 ComponentOptionsFile, 148 ComponentOptionsHLS, 149 ComponentOptionsRecording, 150 ComponentOptionsRTSP, 151 ComponentOptionsSIP, 152 ], 153 ) -> Union[ 154 ComponentFile, ComponentHLS, ComponentRecording, ComponentRTSP, ComponentSIP 155 ]: 156 """ 157 Creates component in the room. 158 Currently there are 4 different components: 159 * File Component for which the options are `ComponentOptionsFile` 160 * HLS Component which options are `ComponentOptionsHLS` 161 * Recording Component which options are `ComponentOptionsRecording` 162 * RTSP Component which options are `ComponentOptionsRTSP` 163 * SIP Component which options are `ComponentOptionsSIP` 164 """ 165 166 if isinstance(options, ComponentOptionsFile): 167 component_type = "file" 168 elif isinstance(options, ComponentOptionsHLS): 169 component_type = "hls" 170 elif isinstance(options, ComponentOptionsRecording): 171 component_type = "recording" 172 elif isinstance(options, ComponentOptionsRTSP): 173 component_type = "rtsp" 174 elif isinstance(options, ComponentOptionsSIP): 175 component_type = "sip" 176 else: 177 raise ValueError( 178 "options must be ComponentOptionsFile, ComponentOptionsHLS," 179 "ComponentOptionsRTSP, ComponentOptionsRecording or ComponentOptionsSIP" 180 ) 181 182 json_body = AddComponentJsonBody(type=component_type, options=options) 183 184 return self._request( 185 room_add_component, room_id=room_id, json_body=json_body 186 ).data
Creates component in the room. Currently there are 4 different components:
- File Component for which the options are
ComponentOptionsFile
- HLS Component which options are
ComponentOptionsHLS
- Recording Component which options are
ComponentOptionsRecording
- RTSP Component which options are
ComponentOptionsRTSP
- SIP Component which options are
ComponentOptionsSIP
188 def delete_component(self, room_id: str, component_id: str) -> None: 189 """Deletes component""" 190 191 return self._request(room_delete_component, id=component_id, room_id=room_id)
Deletes component
193 def subscribe(self, room_id: str, component_id: str, origins: List[str]): 194 """ 195 In order to subscribe the component to peers/components, 196 the component should be initialized with the subscribe_mode set to manual. 197 This mode proves beneficial when you do not wish to record or stream 198 all the available streams within a room. 199 It allows for selective addition instead – 200 you can manually select specific streams. 201 For instance, you could opt to record only the stream of an event's host. 202 """ 203 204 return self._request( 205 subscribe_to, 206 room_id=room_id, 207 component_id=component_id, 208 json_body=SubscriptionConfig(origins=origins), 209 )
In order to subscribe the component to peers/components, the component should be initialized with the subscribe_mode set to manual. This mode proves beneficial when you do not wish to record or stream all the available streams within a room. It allows for selective addition instead – you can manually select specific streams. For instance, you could opt to record only the stream of an event's host.
211 def sip_dial(self, room_id: str, component_id: str, phone_number: str): 212 """ 213 Starts a phone call from a specified component to a provided phone number. 214 215 This is asynchronous operation. 216 In case of providing incorrect phone number you will receive 217 notification `ComponentCrashed`. 218 """ 219 220 return self._request( 221 sip_dial, 222 room_id=room_id, 223 component_id=component_id, 224 json_body=DialConfig(phone_number=phone_number), 225 )
Starts a phone call from a specified component to a provided phone number.
This is asynchronous operation.
In case of providing incorrect phone number you will receive
notification ComponentCrashed
.
227 def sip_end_call(self, room_id: str, component_id: str): 228 """ 229 End a phone call on a specified SIP component. 230 231 This is asynchronous operation. 232 """ 233 234 return self._request( 235 sip_end_call, 236 room_id=room_id, 237 component_id=component_id, 238 )
End a phone call on a specified SIP component.
This is asynchronous operation.
Inherited Members
- jellyfish.api._base_api.BaseApi
- client
10class RecordingApi(BaseApi): 11 """Allows for managing recordings""" 12 13 def __init__( 14 self, 15 server_address: str = "localhost:5002", 16 server_api_token: str = "development", 17 secure: bool = False, 18 ): 19 """ 20 Create RecordingApi instance, providing the jellyfish address and api token. 21 Set secure to `True` for `https` and `False` for `http` connection (default). 22 """ 23 24 super().__init__( 25 server_address=server_address, 26 server_api_token=server_api_token, 27 secure=secure, 28 ) 29 30 def get_list(self) -> list: 31 """Returns a list of available recordings""" 32 33 return self._request(get_recordings).data 34 35 def delete(self, recording_id: str): 36 """Deletes recording with given id""" 37 38 return self._request(delete_recording, recording_id=recording_id)
Allows for managing recordings
13 def __init__( 14 self, 15 server_address: str = "localhost:5002", 16 server_api_token: str = "development", 17 secure: bool = False, 18 ): 19 """ 20 Create RecordingApi instance, providing the jellyfish address and api token. 21 Set secure to `True` for `https` and `False` for `http` connection (default). 22 """ 23 24 super().__init__( 25 server_address=server_address, 26 server_api_token=server_api_token, 27 secure=secure, 28 )
Create RecordingApi instance, providing the jellyfish address and api token.
Set secure to True
for https
and False
for http
connection (default).
30 def get_list(self) -> list: 31 """Returns a list of available recordings""" 32 33 return self._request(get_recordings).data
Returns a list of available recordings
35 def delete(self, recording_id: str): 36 """Deletes recording with given id""" 37 38 return self._request(delete_recording, recording_id=recording_id)
Deletes recording with given id
Inherited Members
- jellyfish.api._base_api.BaseApi
- client
24class Notifier: 25 """ 26 Allows for receiving WebSocket messages from Jellyfish. 27 """ 28 29 def __init__( 30 self, 31 server_address: str = "localhost:5002", 32 server_api_token: str = "development", 33 secure: bool = False, 34 ): 35 """ 36 Create Notifier instance, providing the jellyfish address and api token. 37 Set secure to `True` for `wss` and `False` for `ws` connection (default). 38 """ 39 40 protocol = "wss" if secure else "ws" 41 self._server_address = f"{protocol}://{server_address}/socket/server/websocket" 42 self._server_api_token = server_api_token 43 self._websocket = None 44 self._ready = False 45 46 self._ready_event: asyncio.Event = None 47 48 self._notification_handler: Callable = None 49 self._metrics_handler: Callable = None 50 51 def on_server_notification(self, handler: Callable[[Any], None]): 52 """ 53 Decorator used for defining handler for ServerNotifications 54 i.e. all messages other than `ServerMessageMetricsReport`. 55 """ 56 self._notification_handler = handler 57 return handler 58 59 def on_metrics(self, handler: Callable[[ServerMessageMetricsReport], None]): 60 """ 61 Decorator used for defining handler for `ServerMessageMetricsReport`. 62 """ 63 self._metrics_handler = handler 64 return handler 65 66 async def connect(self): 67 """ 68 A coroutine which connects Notifier to Jellyfish and listens for all incoming 69 messages from the Jellyfish. 70 71 It runs until the connection isn't closed. 72 73 The incoming messages are handled by the functions defined using the 74 `on_server_notification` and `on_metrics` decorators. 75 76 The handlers have to be defined before calling `connect`, 77 otherwise the messages won't be received. 78 """ 79 async with client.connect(self._server_address) as websocket: 80 try: 81 self._websocket = websocket 82 await self._authenticate() 83 84 if self._notification_handler: 85 await self._subscribe_event( 86 event=ServerMessageEventType.EVENT_TYPE_SERVER_NOTIFICATION 87 ) 88 89 if self._metrics_handler: 90 await self._subscribe_event( 91 event=ServerMessageEventType.EVENT_TYPE_METRICS 92 ) 93 94 self._ready = True 95 if self._ready_event: 96 self._ready_event.set() 97 98 await self._receive_loop() 99 finally: 100 self._websocket = None 101 102 async def wait_ready(self) -> True: 103 """ 104 Waits until the notifier is connected and authenticated to Jellyfish. 105 106 If already connected, returns `True` immediately. 107 """ 108 if self._ready: 109 return True 110 111 if self._ready_event is None: 112 self._ready_event = asyncio.Event() 113 114 await self._ready_event.wait() 115 116 async def _authenticate(self): 117 msg = ServerMessage( 118 auth_request=ServerMessageAuthRequest(token=self._server_api_token) 119 ) 120 await self._websocket.send(bytes(msg)) 121 122 try: 123 message = await self._websocket.recv() 124 except ConnectionClosed as exception: 125 if "invalid token" in str(exception): 126 raise RuntimeError("Invalid server_api_token") from exception 127 raise 128 129 message = ServerMessage().parse(message) 130 131 _type, message = betterproto.which_one_of(message, "content") 132 assert isinstance(message, ServerMessageAuthenticated) 133 134 async def _receive_loop(self): 135 while True: 136 message = await self._websocket.recv() 137 message = ServerMessage().parse(message) 138 _which, message = betterproto.which_one_of(message, "content") 139 140 if isinstance(message, ServerMessageMetricsReport): 141 self._metrics_handler(message) 142 else: 143 self._notification_handler(message) 144 145 async def _subscribe_event(self, event: ServerMessageEventType): 146 request = ServerMessage(subscribe_request=ServerMessageSubscribeRequest(event)) 147 148 await self._websocket.send(bytes(request)) 149 message = await self._websocket.recv() 150 message = ServerMessage().parse(message) 151 _which, message = betterproto.which_one_of(message, "content") 152 assert isinstance(message, ServerMessageSubscribeResponse)
Allows for receiving WebSocket messages from Jellyfish.
29 def __init__( 30 self, 31 server_address: str = "localhost:5002", 32 server_api_token: str = "development", 33 secure: bool = False, 34 ): 35 """ 36 Create Notifier instance, providing the jellyfish address and api token. 37 Set secure to `True` for `wss` and `False` for `ws` connection (default). 38 """ 39 40 protocol = "wss" if secure else "ws" 41 self._server_address = f"{protocol}://{server_address}/socket/server/websocket" 42 self._server_api_token = server_api_token 43 self._websocket = None 44 self._ready = False 45 46 self._ready_event: asyncio.Event = None 47 48 self._notification_handler: Callable = None 49 self._metrics_handler: Callable = None
Create Notifier instance, providing the jellyfish address and api token.
Set secure to True
for wss
and False
for ws
connection (default).
51 def on_server_notification(self, handler: Callable[[Any], None]): 52 """ 53 Decorator used for defining handler for ServerNotifications 54 i.e. all messages other than `ServerMessageMetricsReport`. 55 """ 56 self._notification_handler = handler 57 return handler
Decorator used for defining handler for ServerNotifications
i.e. all messages other than ServerMessageMetricsReport
.
59 def on_metrics(self, handler: Callable[[ServerMessageMetricsReport], None]): 60 """ 61 Decorator used for defining handler for `ServerMessageMetricsReport`. 62 """ 63 self._metrics_handler = handler 64 return handler
Decorator used for defining handler for ServerMessageMetricsReport
.
66 async def connect(self): 67 """ 68 A coroutine which connects Notifier to Jellyfish and listens for all incoming 69 messages from the Jellyfish. 70 71 It runs until the connection isn't closed. 72 73 The incoming messages are handled by the functions defined using the 74 `on_server_notification` and `on_metrics` decorators. 75 76 The handlers have to be defined before calling `connect`, 77 otherwise the messages won't be received. 78 """ 79 async with client.connect(self._server_address) as websocket: 80 try: 81 self._websocket = websocket 82 await self._authenticate() 83 84 if self._notification_handler: 85 await self._subscribe_event( 86 event=ServerMessageEventType.EVENT_TYPE_SERVER_NOTIFICATION 87 ) 88 89 if self._metrics_handler: 90 await self._subscribe_event( 91 event=ServerMessageEventType.EVENT_TYPE_METRICS 92 ) 93 94 self._ready = True 95 if self._ready_event: 96 self._ready_event.set() 97 98 await self._receive_loop() 99 finally: 100 self._websocket = None
A coroutine which connects Notifier to Jellyfish and listens for all incoming messages from the Jellyfish.
It runs until the connection isn't closed.
The incoming messages are handled by the functions defined using the
on_server_notification
and on_metrics
decorators.
The handlers have to be defined before calling connect
,
otherwise the messages won't be received.
102 async def wait_ready(self) -> True: 103 """ 104 Waits until the notifier is connected and authenticated to Jellyfish. 105 106 If already connected, returns `True` immediately. 107 """ 108 if self._ready: 109 return True 110 111 if self._ready_event is None: 112 self._ready_event = asyncio.Event() 113 114 await self._ready_event.wait()
Waits until the notifier is connected and authenticated to Jellyfish.
If already connected, returns True
immediately.
12def receive_binary(binary: bytes) -> betterproto.Message: 13 """ 14 Transform received protobuf notification to adequate notification instance. 15 16 The available notifications are listed in `jellyfish.events` module. 17 """ 18 message = ServerMessage().parse(binary) 19 _which, message = betterproto.which_one_of(message, "content") 20 return message
Transform received protobuf notification to adequate notification instance.
The available notifications are listed in jellyfish.events
module.
28@_attrs_define 29class Room: 30 """Description of the room state""" 31 32 components: List[ 33 Union[ 34 "ComponentFile", 35 "ComponentHLS", 36 "ComponentRTSP", 37 "ComponentRecording", 38 "ComponentSIP", 39 ] 40 ] 41 """List of all components""" 42 config: "RoomConfig" 43 """Room configuration""" 44 id: str 45 """Room ID""" 46 peers: List["Peer"] 47 """List of all peers""" 48 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 49 """@private""" 50 51 def to_dict(self) -> Dict[str, Any]: 52 """@private""" 53 from ..models.component_file import ComponentFile 54 from ..models.component_hls import ComponentHLS 55 from ..models.component_rtsp import ComponentRTSP 56 from ..models.component_sip import ComponentSIP 57 58 components = [] 59 for components_item_data in self.components: 60 components_item: Dict[str, Any] 61 62 if isinstance(components_item_data, ComponentHLS): 63 components_item = components_item_data.to_dict() 64 65 elif isinstance(components_item_data, ComponentRTSP): 66 components_item = components_item_data.to_dict() 67 68 elif isinstance(components_item_data, ComponentFile): 69 components_item = components_item_data.to_dict() 70 71 elif isinstance(components_item_data, ComponentSIP): 72 components_item = components_item_data.to_dict() 73 74 else: 75 components_item = components_item_data.to_dict() 76 77 components.append(components_item) 78 79 config = self.config.to_dict() 80 81 id = self.id 82 peers = [] 83 for peers_item_data in self.peers: 84 peers_item = peers_item_data.to_dict() 85 86 peers.append(peers_item) 87 88 field_dict: Dict[str, Any] = {} 89 field_dict.update(self.additional_properties) 90 field_dict.update( 91 { 92 "components": components, 93 "config": config, 94 "id": id, 95 "peers": peers, 96 } 97 ) 98 99 return field_dict 100 101 @classmethod 102 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 103 """@private""" 104 from ..models.component_file import ComponentFile 105 from ..models.component_hls import ComponentHLS 106 from ..models.component_recording import ComponentRecording 107 from ..models.component_rtsp import ComponentRTSP 108 from ..models.component_sip import ComponentSIP 109 from ..models.peer import Peer 110 from ..models.room_config import RoomConfig 111 112 d = src_dict.copy() 113 components = [] 114 _components = d.pop("components") 115 for components_item_data in _components: 116 117 def _parse_components_item( 118 data: object, 119 ) -> Union[ 120 "ComponentFile", 121 "ComponentHLS", 122 "ComponentRTSP", 123 "ComponentRecording", 124 "ComponentSIP", 125 ]: 126 try: 127 if not isinstance(data, dict): 128 raise TypeError() 129 componentsschemas_component_type_0 = ComponentHLS.from_dict(data) 130 131 return componentsschemas_component_type_0 132 except: # noqa: E722 133 pass 134 try: 135 if not isinstance(data, dict): 136 raise TypeError() 137 componentsschemas_component_type_1 = ComponentRTSP.from_dict(data) 138 139 return componentsschemas_component_type_1 140 except: # noqa: E722 141 pass 142 try: 143 if not isinstance(data, dict): 144 raise TypeError() 145 componentsschemas_component_type_2 = ComponentFile.from_dict(data) 146 147 return componentsschemas_component_type_2 148 except: # noqa: E722 149 pass 150 try: 151 if not isinstance(data, dict): 152 raise TypeError() 153 componentsschemas_component_type_3 = ComponentSIP.from_dict(data) 154 155 return componentsschemas_component_type_3 156 except: # noqa: E722 157 pass 158 if not isinstance(data, dict): 159 raise TypeError() 160 componentsschemas_component_type_4 = ComponentRecording.from_dict(data) 161 162 return componentsschemas_component_type_4 163 164 components_item = _parse_components_item(components_item_data) 165 166 components.append(components_item) 167 168 config = RoomConfig.from_dict(d.pop("config")) 169 170 id = d.pop("id") 171 172 peers = [] 173 _peers = d.pop("peers") 174 for peers_item_data in _peers: 175 peers_item = Peer.from_dict(peers_item_data) 176 177 peers.append(peers_item) 178 179 room = cls( 180 components=components, 181 config=config, 182 id=id, 183 peers=peers, 184 ) 185 186 room.additional_properties = d 187 return room 188 189 @property 190 def additional_keys(self) -> List[str]: 191 """@private""" 192 return list(self.additional_properties.keys()) 193 194 def __getitem__(self, key: str) -> Any: 195 return self.additional_properties[key] 196 197 def __setitem__(self, key: str, value: Any) -> None: 198 self.additional_properties[key] = value 199 200 def __delitem__(self, key: str) -> None: 201 del self.additional_properties[key] 202 203 def __contains__(self, key: str) -> bool: 204 return key in self.additional_properties
Description of the room state
2def __init__(self, components, config, id, peers): 3 self.components = components 4 self.config = config 5 self.id = id 6 self.peers = peers 7 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class Room.
List of all components
13@_attrs_define 14class RoomConfig: 15 """Room configuration""" 16 17 max_peers: Union[Unset, None, int] = UNSET 18 """Maximum amount of peers allowed into the room""" 19 peer_disconnected_timeout: Union[Unset, None, int] = UNSET 20 """Duration (in seconds) after which the peer will be removed if it is disconnected. If not provided, this feature is disabled.""" 21 peerless_purge_timeout: Union[Unset, None, int] = UNSET 22 """Duration (in seconds) after which the room will be removed if no peers are connected. If not provided, this feature is disabled.""" 23 room_id: Union[Unset, None, str] = UNSET 24 """Custom id used for identifying room within Jellyfish. Must be unique across all rooms. If not provided, random UUID is generated.""" 25 video_codec: Union[Unset, None, RoomConfigVideoCodec] = UNSET 26 """Enforces video codec for each peer in the room""" 27 webhook_url: Union[Unset, None, str] = UNSET 28 """URL where Jellyfish notifications will be sent""" 29 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 30 """@private""" 31 32 def to_dict(self) -> Dict[str, Any]: 33 """@private""" 34 max_peers = self.max_peers 35 peer_disconnected_timeout = self.peer_disconnected_timeout 36 peerless_purge_timeout = self.peerless_purge_timeout 37 room_id = self.room_id 38 video_codec: Union[Unset, None, str] = UNSET 39 if not isinstance(self.video_codec, Unset): 40 video_codec = self.video_codec.value if self.video_codec else None 41 42 webhook_url = self.webhook_url 43 44 field_dict: Dict[str, Any] = {} 45 field_dict.update(self.additional_properties) 46 field_dict.update({}) 47 if max_peers is not UNSET: 48 field_dict["maxPeers"] = max_peers 49 if peer_disconnected_timeout is not UNSET: 50 field_dict["peerDisconnectedTimeout"] = peer_disconnected_timeout 51 if peerless_purge_timeout is not UNSET: 52 field_dict["peerlessPurgeTimeout"] = peerless_purge_timeout 53 if room_id is not UNSET: 54 field_dict["roomId"] = room_id 55 if video_codec is not UNSET: 56 field_dict["videoCodec"] = video_codec 57 if webhook_url is not UNSET: 58 field_dict["webhookUrl"] = webhook_url 59 60 return field_dict 61 62 @classmethod 63 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 64 """@private""" 65 d = src_dict.copy() 66 max_peers = d.pop("maxPeers", UNSET) 67 68 peer_disconnected_timeout = d.pop("peerDisconnectedTimeout", UNSET) 69 70 peerless_purge_timeout = d.pop("peerlessPurgeTimeout", UNSET) 71 72 room_id = d.pop("roomId", UNSET) 73 74 _video_codec = d.pop("videoCodec", UNSET) 75 video_codec: Union[Unset, None, RoomConfigVideoCodec] 76 if _video_codec is None: 77 video_codec = None 78 elif isinstance(_video_codec, Unset): 79 video_codec = UNSET 80 else: 81 video_codec = RoomConfigVideoCodec(_video_codec) 82 83 webhook_url = d.pop("webhookUrl", UNSET) 84 85 room_config = cls( 86 max_peers=max_peers, 87 peer_disconnected_timeout=peer_disconnected_timeout, 88 peerless_purge_timeout=peerless_purge_timeout, 89 room_id=room_id, 90 video_codec=video_codec, 91 webhook_url=webhook_url, 92 ) 93 94 room_config.additional_properties = d 95 return room_config 96 97 @property 98 def additional_keys(self) -> List[str]: 99 """@private""" 100 return list(self.additional_properties.keys()) 101 102 def __getitem__(self, key: str) -> Any: 103 return self.additional_properties[key] 104 105 def __setitem__(self, key: str, value: Any) -> None: 106 self.additional_properties[key] = value 107 108 def __delitem__(self, key: str) -> None: 109 del self.additional_properties[key] 110 111 def __contains__(self, key: str) -> bool: 112 return key in self.additional_properties
Room configuration
2def __init__(self, max_peers=attr_dict['max_peers'].default, peer_disconnected_timeout=attr_dict['peer_disconnected_timeout'].default, peerless_purge_timeout=attr_dict['peerless_purge_timeout'].default, room_id=attr_dict['room_id'].default, video_codec=attr_dict['video_codec'].default, webhook_url=attr_dict['webhook_url'].default): 3 self.max_peers = max_peers 4 self.peer_disconnected_timeout = peer_disconnected_timeout 5 self.peerless_purge_timeout = peerless_purge_timeout 6 self.room_id = room_id 7 self.video_codec = video_codec 8 self.webhook_url = webhook_url 9 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class RoomConfig.
Maximum amount of peers allowed into the room
Duration (in seconds) after which the peer will be removed if it is disconnected. If not provided, this feature is disabled.
Duration (in seconds) after which the room will be removed if no peers are connected. If not provided, this feature is disabled.
Custom id used for identifying room within Jellyfish. Must be unique across all rooms. If not provided, random UUID is generated.
Enforces video codec for each peer in the room
5class RoomConfigVideoCodec(str, Enum): 6 """Enforces video codec for each peer in the room""" 7 8 H264 = "h264" 9 VP8 = "vp8" 10 11 def __str__(self) -> str: 12 return str(self.value)
Enforces video codec for each peer in the room
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
16@_attrs_define 17class Peer: 18 """Describes peer status""" 19 20 id: str 21 """Assigned peer id""" 22 metadata: Any 23 """Custom metadata set by the peer""" 24 status: PeerStatus 25 """Informs about the peer status""" 26 tracks: List["Track"] 27 """List of all peer's tracks""" 28 type: str 29 """Peer type""" 30 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 31 """@private""" 32 33 def to_dict(self) -> Dict[str, Any]: 34 """@private""" 35 id = self.id 36 metadata = self.metadata 37 status = self.status.value 38 39 tracks = [] 40 for tracks_item_data in self.tracks: 41 tracks_item = tracks_item_data.to_dict() 42 43 tracks.append(tracks_item) 44 45 type = self.type 46 47 field_dict: Dict[str, Any] = {} 48 field_dict.update(self.additional_properties) 49 field_dict.update( 50 { 51 "id": id, 52 "metadata": metadata, 53 "status": status, 54 "tracks": tracks, 55 "type": type, 56 } 57 ) 58 59 return field_dict 60 61 @classmethod 62 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 63 """@private""" 64 from ..models.track import Track 65 66 d = src_dict.copy() 67 id = d.pop("id") 68 69 metadata = d.pop("metadata") 70 71 status = PeerStatus(d.pop("status")) 72 73 tracks = [] 74 _tracks = d.pop("tracks") 75 for tracks_item_data in _tracks: 76 tracks_item = Track.from_dict(tracks_item_data) 77 78 tracks.append(tracks_item) 79 80 type = d.pop("type") 81 82 peer = cls( 83 id=id, 84 metadata=metadata, 85 status=status, 86 tracks=tracks, 87 type=type, 88 ) 89 90 peer.additional_properties = d 91 return peer 92 93 @property 94 def additional_keys(self) -> List[str]: 95 """@private""" 96 return list(self.additional_properties.keys()) 97 98 def __getitem__(self, key: str) -> Any: 99 return self.additional_properties[key] 100 101 def __setitem__(self, key: str, value: Any) -> None: 102 self.additional_properties[key] = value 103 104 def __delitem__(self, key: str) -> None: 105 del self.additional_properties[key] 106 107 def __contains__(self, key: str) -> bool: 108 return key in self.additional_properties
Describes peer status
2def __init__(self, id, metadata, status, tracks, type): 3 self.id = id 4 self.metadata = metadata 5 self.status = status 6 self.tracks = tracks 7 self.type = type 8 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class Peer.
12@_attrs_define 13class PeerOptionsWebRTC: 14 """Options specific to the WebRTC peer""" 15 16 enable_simulcast: Union[Unset, bool] = True 17 """Enables the peer to use simulcast""" 18 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 19 """@private""" 20 21 def to_dict(self) -> Dict[str, Any]: 22 """@private""" 23 enable_simulcast = self.enable_simulcast 24 25 field_dict: Dict[str, Any] = {} 26 field_dict.update(self.additional_properties) 27 field_dict.update({}) 28 if enable_simulcast is not UNSET: 29 field_dict["enableSimulcast"] = enable_simulcast 30 31 return field_dict 32 33 @classmethod 34 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 35 """@private""" 36 d = src_dict.copy() 37 enable_simulcast = d.pop("enableSimulcast", UNSET) 38 39 peer_options_web_rtc = cls( 40 enable_simulcast=enable_simulcast, 41 ) 42 43 peer_options_web_rtc.additional_properties = d 44 return peer_options_web_rtc 45 46 @property 47 def additional_keys(self) -> List[str]: 48 """@private""" 49 return list(self.additional_properties.keys()) 50 51 def __getitem__(self, key: str) -> Any: 52 return self.additional_properties[key] 53 54 def __setitem__(self, key: str, value: Any) -> None: 55 self.additional_properties[key] = value 56 57 def __delitem__(self, key: str) -> None: 58 del self.additional_properties[key] 59 60 def __contains__(self, key: str) -> bool: 61 return key in self.additional_properties
Options specific to the WebRTC peer
2def __init__(self, enable_simulcast=attr_dict['enable_simulcast'].default): 3 self.enable_simulcast = enable_simulcast 4 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class PeerOptionsWebRTC.
5class PeerStatus(str, Enum): 6 """Informs about the peer status""" 7 8 CONNECTED = "connected" 9 DISCONNECTED = "disconnected" 10 11 def __str__(self) -> str: 12 return str(self.value)
Informs about the peer status
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
15@_attrs_define 16class ComponentHLS: 17 """Describes the HLS component""" 18 19 id: str 20 """Assigned component ID""" 21 properties: "ComponentPropertiesHLS" 22 """Properties specific to the HLS component""" 23 tracks: List["Track"] 24 """List of all component's tracks""" 25 type: str 26 """Component type""" 27 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 28 """@private""" 29 30 def to_dict(self) -> Dict[str, Any]: 31 """@private""" 32 id = self.id 33 properties = self.properties.to_dict() 34 35 tracks = [] 36 for tracks_item_data in self.tracks: 37 tracks_item = tracks_item_data.to_dict() 38 39 tracks.append(tracks_item) 40 41 type = self.type 42 43 field_dict: Dict[str, Any] = {} 44 field_dict.update(self.additional_properties) 45 field_dict.update( 46 { 47 "id": id, 48 "properties": properties, 49 "tracks": tracks, 50 "type": type, 51 } 52 ) 53 54 return field_dict 55 56 @classmethod 57 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 58 """@private""" 59 from ..models.component_properties_hls import ComponentPropertiesHLS 60 from ..models.track import Track 61 62 d = src_dict.copy() 63 id = d.pop("id") 64 65 properties = ComponentPropertiesHLS.from_dict(d.pop("properties")) 66 67 tracks = [] 68 _tracks = d.pop("tracks") 69 for tracks_item_data in _tracks: 70 tracks_item = Track.from_dict(tracks_item_data) 71 72 tracks.append(tracks_item) 73 74 type = d.pop("type") 75 76 component_hls = cls( 77 id=id, 78 properties=properties, 79 tracks=tracks, 80 type=type, 81 ) 82 83 component_hls.additional_properties = d 84 return component_hls 85 86 @property 87 def additional_keys(self) -> List[str]: 88 """@private""" 89 return list(self.additional_properties.keys()) 90 91 def __getitem__(self, key: str) -> Any: 92 return self.additional_properties[key] 93 94 def __setitem__(self, key: str, value: Any) -> None: 95 self.additional_properties[key] = value 96 97 def __delitem__(self, key: str) -> None: 98 del self.additional_properties[key] 99 100 def __contains__(self, key: str) -> bool: 101 return key in self.additional_properties
Describes the HLS component
2def __init__(self, id, properties, tracks, type): 3 self.id = id 4 self.properties = properties 5 self.tracks = tracks 6 self.type = type 7 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentHLS.
19@_attrs_define 20class ComponentOptionsHLS: 21 """Options specific to the HLS component""" 22 23 low_latency: Union[Unset, bool] = False 24 """Whether the component should use LL-HLS""" 25 persistent: Union[Unset, bool] = False 26 """Whether the video is stored after end of stream""" 27 s3: Union[Unset, None, "S3Credentials"] = UNSET 28 """An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided""" 29 subscribe_mode: Union[ 30 Unset, ComponentOptionsHLSSubscribeMode 31 ] = ComponentOptionsHLSSubscribeMode.AUTO 32 """Whether the HLS component should subscribe to tracks automatically or manually.""" 33 target_window_duration: Union[Unset, None, int] = UNSET 34 """Duration of stream available for viewer""" 35 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 36 """@private""" 37 38 def to_dict(self) -> Dict[str, Any]: 39 """@private""" 40 low_latency = self.low_latency 41 persistent = self.persistent 42 s3: Union[Unset, None, Dict[str, Any]] = UNSET 43 if not isinstance(self.s3, Unset): 44 s3 = self.s3.to_dict() if self.s3 else None 45 46 subscribe_mode: Union[Unset, str] = UNSET 47 if not isinstance(self.subscribe_mode, Unset): 48 subscribe_mode = self.subscribe_mode.value 49 50 target_window_duration = self.target_window_duration 51 52 field_dict: Dict[str, Any] = {} 53 field_dict.update(self.additional_properties) 54 field_dict.update({}) 55 if low_latency is not UNSET: 56 field_dict["lowLatency"] = low_latency 57 if persistent is not UNSET: 58 field_dict["persistent"] = persistent 59 if s3 is not UNSET: 60 field_dict["s3"] = s3 61 if subscribe_mode is not UNSET: 62 field_dict["subscribeMode"] = subscribe_mode 63 if target_window_duration is not UNSET: 64 field_dict["targetWindowDuration"] = target_window_duration 65 66 return field_dict 67 68 @classmethod 69 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 70 """@private""" 71 from ..models.s3_credentials import S3Credentials 72 73 d = src_dict.copy() 74 low_latency = d.pop("lowLatency", UNSET) 75 76 persistent = d.pop("persistent", UNSET) 77 78 _s3 = d.pop("s3", UNSET) 79 s3: Union[Unset, None, S3Credentials] 80 if _s3 is None: 81 s3 = None 82 elif isinstance(_s3, Unset): 83 s3 = UNSET 84 else: 85 s3 = S3Credentials.from_dict(_s3) 86 87 _subscribe_mode = d.pop("subscribeMode", UNSET) 88 subscribe_mode: Union[Unset, ComponentOptionsHLSSubscribeMode] 89 if isinstance(_subscribe_mode, Unset): 90 subscribe_mode = UNSET 91 else: 92 subscribe_mode = ComponentOptionsHLSSubscribeMode(_subscribe_mode) 93 94 target_window_duration = d.pop("targetWindowDuration", UNSET) 95 96 component_options_hls = cls( 97 low_latency=low_latency, 98 persistent=persistent, 99 s3=s3, 100 subscribe_mode=subscribe_mode, 101 target_window_duration=target_window_duration, 102 ) 103 104 component_options_hls.additional_properties = d 105 return component_options_hls 106 107 @property 108 def additional_keys(self) -> List[str]: 109 """@private""" 110 return list(self.additional_properties.keys()) 111 112 def __getitem__(self, key: str) -> Any: 113 return self.additional_properties[key] 114 115 def __setitem__(self, key: str, value: Any) -> None: 116 self.additional_properties[key] = value 117 118 def __delitem__(self, key: str) -> None: 119 del self.additional_properties[key] 120 121 def __contains__(self, key: str) -> bool: 122 return key in self.additional_properties
Options specific to the HLS component
2def __init__(self, low_latency=attr_dict['low_latency'].default, persistent=attr_dict['persistent'].default, s3=attr_dict['s3'].default, subscribe_mode=attr_dict['subscribe_mode'].default, target_window_duration=attr_dict['target_window_duration'].default): 3 self.low_latency = low_latency 4 self.persistent = persistent 5 self.s3 = s3 6 self.subscribe_mode = subscribe_mode 7 self.target_window_duration = target_window_duration 8 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentOptionsHLS.
Whether the component should use LL-HLS
Whether the video is stored after end of stream
An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided
Whether the HLS component should subscribe to tracks automatically or manually.
5class ComponentOptionsHLSSubscribeMode(str, Enum): 6 """Whether the HLS component should subscribe to tracks automatically or manually.""" 7 8 AUTO = "auto" 9 MANUAL = "manual" 10 11 def __str__(self) -> str: 12 return str(self.value)
Whether the HLS component should subscribe to tracks automatically or manually.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
14@_attrs_define 15class ComponentPropertiesHLS: 16 """Properties specific to the HLS component""" 17 18 low_latency: bool 19 """Whether the component uses LL-HLS""" 20 persistent: bool 21 """Whether the video is stored after end of stream""" 22 playable: bool 23 """Whether the generated HLS playlist is playable""" 24 subscribe_mode: ComponentPropertiesHLSSubscribeMode 25 """Whether the HLS component should subscribe to tracks automatically or manually""" 26 target_window_duration: Optional[int] 27 """Duration of stream available for viewer""" 28 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 29 """@private""" 30 31 def to_dict(self) -> Dict[str, Any]: 32 """@private""" 33 low_latency = self.low_latency 34 persistent = self.persistent 35 playable = self.playable 36 subscribe_mode = self.subscribe_mode.value 37 38 target_window_duration = self.target_window_duration 39 40 field_dict: Dict[str, Any] = {} 41 field_dict.update(self.additional_properties) 42 field_dict.update( 43 { 44 "lowLatency": low_latency, 45 "persistent": persistent, 46 "playable": playable, 47 "subscribeMode": subscribe_mode, 48 "targetWindowDuration": target_window_duration, 49 } 50 ) 51 52 return field_dict 53 54 @classmethod 55 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 56 """@private""" 57 d = src_dict.copy() 58 low_latency = d.pop("lowLatency") 59 60 persistent = d.pop("persistent") 61 62 playable = d.pop("playable") 63 64 subscribe_mode = ComponentPropertiesHLSSubscribeMode(d.pop("subscribeMode")) 65 66 target_window_duration = d.pop("targetWindowDuration") 67 68 component_properties_hls = cls( 69 low_latency=low_latency, 70 persistent=persistent, 71 playable=playable, 72 subscribe_mode=subscribe_mode, 73 target_window_duration=target_window_duration, 74 ) 75 76 component_properties_hls.additional_properties = d 77 return component_properties_hls 78 79 @property 80 def additional_keys(self) -> List[str]: 81 """@private""" 82 return list(self.additional_properties.keys()) 83 84 def __getitem__(self, key: str) -> Any: 85 return self.additional_properties[key] 86 87 def __setitem__(self, key: str, value: Any) -> None: 88 self.additional_properties[key] = value 89 90 def __delitem__(self, key: str) -> None: 91 del self.additional_properties[key] 92 93 def __contains__(self, key: str) -> bool: 94 return key in self.additional_properties
Properties specific to the HLS component
2def __init__(self, low_latency, persistent, playable, subscribe_mode, target_window_duration): 3 self.low_latency = low_latency 4 self.persistent = persistent 5 self.playable = playable 6 self.subscribe_mode = subscribe_mode 7 self.target_window_duration = target_window_duration 8 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentPropertiesHLS.
Whether the HLS component should subscribe to tracks automatically or manually
5class ComponentPropertiesHLSSubscribeMode(str, Enum): 6 """Whether the HLS component should subscribe to tracks automatically or manually""" 7 8 AUTO = "auto" 9 MANUAL = "manual" 10 11 def __str__(self) -> str: 12 return str(self.value)
Whether the HLS component should subscribe to tracks automatically or manually
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
15@_attrs_define 16class ComponentSIP: 17 """Describes the SIP component""" 18 19 id: str 20 """Assigned component ID""" 21 properties: "ComponentPropertiesSIP" 22 """Properties specific to the SIP component""" 23 tracks: List["Track"] 24 """List of all component's tracks""" 25 type: str 26 """Component type""" 27 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 28 """@private""" 29 30 def to_dict(self) -> Dict[str, Any]: 31 """@private""" 32 id = self.id 33 properties = self.properties.to_dict() 34 35 tracks = [] 36 for tracks_item_data in self.tracks: 37 tracks_item = tracks_item_data.to_dict() 38 39 tracks.append(tracks_item) 40 41 type = self.type 42 43 field_dict: Dict[str, Any] = {} 44 field_dict.update(self.additional_properties) 45 field_dict.update( 46 { 47 "id": id, 48 "properties": properties, 49 "tracks": tracks, 50 "type": type, 51 } 52 ) 53 54 return field_dict 55 56 @classmethod 57 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 58 """@private""" 59 from ..models.component_properties_sip import ComponentPropertiesSIP 60 from ..models.track import Track 61 62 d = src_dict.copy() 63 id = d.pop("id") 64 65 properties = ComponentPropertiesSIP.from_dict(d.pop("properties")) 66 67 tracks = [] 68 _tracks = d.pop("tracks") 69 for tracks_item_data in _tracks: 70 tracks_item = Track.from_dict(tracks_item_data) 71 72 tracks.append(tracks_item) 73 74 type = d.pop("type") 75 76 component_sip = cls( 77 id=id, 78 properties=properties, 79 tracks=tracks, 80 type=type, 81 ) 82 83 component_sip.additional_properties = d 84 return component_sip 85 86 @property 87 def additional_keys(self) -> List[str]: 88 """@private""" 89 return list(self.additional_properties.keys()) 90 91 def __getitem__(self, key: str) -> Any: 92 return self.additional_properties[key] 93 94 def __setitem__(self, key: str, value: Any) -> None: 95 self.additional_properties[key] = value 96 97 def __delitem__(self, key: str) -> None: 98 del self.additional_properties[key] 99 100 def __contains__(self, key: str) -> bool: 101 return key in self.additional_properties
Describes the SIP component
2def __init__(self, id, properties, tracks, type): 3 self.id = id 4 self.properties = properties 5 self.tracks = tracks 6 self.type = type 7 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentSIP.
16@_attrs_define 17class ComponentOptionsSIP: 18 """Options specific to the SIP component""" 19 20 registrar_credentials: "ComponentOptionsSIPSIPCredentials" 21 """Credentials used to authorize in SIP Provider service""" 22 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 23 """@private""" 24 25 def to_dict(self) -> Dict[str, Any]: 26 """@private""" 27 registrar_credentials = self.registrar_credentials.to_dict() 28 29 field_dict: Dict[str, Any] = {} 30 field_dict.update(self.additional_properties) 31 field_dict.update( 32 { 33 "registrarCredentials": registrar_credentials, 34 } 35 ) 36 37 return field_dict 38 39 @classmethod 40 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 41 """@private""" 42 from ..models.component_options_sipsip_credentials import ( 43 ComponentOptionsSIPSIPCredentials, 44 ) 45 46 d = src_dict.copy() 47 registrar_credentials = ComponentOptionsSIPSIPCredentials.from_dict( 48 d.pop("registrarCredentials") 49 ) 50 51 component_options_sip = cls( 52 registrar_credentials=registrar_credentials, 53 ) 54 55 component_options_sip.additional_properties = d 56 return component_options_sip 57 58 @property 59 def additional_keys(self) -> List[str]: 60 """@private""" 61 return list(self.additional_properties.keys()) 62 63 def __getitem__(self, key: str) -> Any: 64 return self.additional_properties[key] 65 66 def __setitem__(self, key: str, value: Any) -> None: 67 self.additional_properties[key] = value 68 69 def __delitem__(self, key: str) -> None: 70 del self.additional_properties[key] 71 72 def __contains__(self, key: str) -> bool: 73 return key in self.additional_properties
Options specific to the SIP component
2def __init__(self, registrar_credentials): 3 self.registrar_credentials = registrar_credentials 4 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentOptionsSIP.
16@_attrs_define 17class ComponentPropertiesSIP: 18 """Properties specific to the SIP component""" 19 20 registrar_credentials: "ComponentPropertiesSIPSIPCredentials" 21 """Credentials used to authorize in SIP Provider service""" 22 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 23 """@private""" 24 25 def to_dict(self) -> Dict[str, Any]: 26 """@private""" 27 registrar_credentials = self.registrar_credentials.to_dict() 28 29 field_dict: Dict[str, Any] = {} 30 field_dict.update(self.additional_properties) 31 field_dict.update( 32 { 33 "registrarCredentials": registrar_credentials, 34 } 35 ) 36 37 return field_dict 38 39 @classmethod 40 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 41 """@private""" 42 from ..models.component_properties_sipsip_credentials import ( 43 ComponentPropertiesSIPSIPCredentials, 44 ) 45 46 d = src_dict.copy() 47 registrar_credentials = ComponentPropertiesSIPSIPCredentials.from_dict( 48 d.pop("registrarCredentials") 49 ) 50 51 component_properties_sip = cls( 52 registrar_credentials=registrar_credentials, 53 ) 54 55 component_properties_sip.additional_properties = d 56 return component_properties_sip 57 58 @property 59 def additional_keys(self) -> List[str]: 60 """@private""" 61 return list(self.additional_properties.keys()) 62 63 def __getitem__(self, key: str) -> Any: 64 return self.additional_properties[key] 65 66 def __setitem__(self, key: str, value: Any) -> None: 67 self.additional_properties[key] = value 68 69 def __delitem__(self, key: str) -> None: 70 del self.additional_properties[key] 71 72 def __contains__(self, key: str) -> bool: 73 return key in self.additional_properties
Properties specific to the SIP component
2def __init__(self, registrar_credentials): 3 self.registrar_credentials = registrar_credentials 4 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentPropertiesSIP.
Credentials used to authorize in SIP Provider service
10@_attrs_define 11class ComponentPropertiesSIPSIPCredentials: 12 """Credentials used to authorize in SIP Provider service""" 13 14 address: str 15 """SIP provider address. Can be in the form of FQDN (my-sip-registrar.net) or IPv4 (1.2.3.4). Port can be specified e.g: 5.6.7.8:9999. If not given, the default SIP port `5060` will be assumed""" 16 password: str 17 """Password in SIP service provider""" 18 username: str 19 """Username in SIP service provider""" 20 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 21 """@private""" 22 23 def to_dict(self) -> Dict[str, Any]: 24 """@private""" 25 address = self.address 26 password = self.password 27 username = self.username 28 29 field_dict: Dict[str, Any] = {} 30 field_dict.update(self.additional_properties) 31 field_dict.update( 32 { 33 "address": address, 34 "password": password, 35 "username": username, 36 } 37 ) 38 39 return field_dict 40 41 @classmethod 42 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 43 """@private""" 44 d = src_dict.copy() 45 address = d.pop("address") 46 47 password = d.pop("password") 48 49 username = d.pop("username") 50 51 component_properties_sipsip_credentials = cls( 52 address=address, 53 password=password, 54 username=username, 55 ) 56 57 component_properties_sipsip_credentials.additional_properties = d 58 return component_properties_sipsip_credentials 59 60 @property 61 def additional_keys(self) -> List[str]: 62 """@private""" 63 return list(self.additional_properties.keys()) 64 65 def __getitem__(self, key: str) -> Any: 66 return self.additional_properties[key] 67 68 def __setitem__(self, key: str, value: Any) -> None: 69 self.additional_properties[key] = value 70 71 def __delitem__(self, key: str) -> None: 72 del self.additional_properties[key] 73 74 def __contains__(self, key: str) -> bool: 75 return key in self.additional_properties
Credentials used to authorize in SIP Provider service
2def __init__(self, address, password, username): 3 self.address = address 4 self.password = password 5 self.username = username 6 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentPropertiesSIPSIPCredentials.
17@_attrs_define 18class ComponentFile: 19 """Describes the File component""" 20 21 id: str 22 """Assigned component ID""" 23 tracks: List["Track"] 24 """List of all component's tracks""" 25 type: str 26 """Component type""" 27 properties: Union[Unset, "ComponentPropertiesFile"] = UNSET 28 """Properties specific to the File component""" 29 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 30 """@private""" 31 32 def to_dict(self) -> Dict[str, Any]: 33 """@private""" 34 id = self.id 35 tracks = [] 36 for tracks_item_data in self.tracks: 37 tracks_item = tracks_item_data.to_dict() 38 39 tracks.append(tracks_item) 40 41 type = self.type 42 properties: Union[Unset, Dict[str, Any]] = UNSET 43 if not isinstance(self.properties, Unset): 44 properties = self.properties.to_dict() 45 46 field_dict: Dict[str, Any] = {} 47 field_dict.update(self.additional_properties) 48 field_dict.update( 49 { 50 "id": id, 51 "tracks": tracks, 52 "type": type, 53 } 54 ) 55 if properties is not UNSET: 56 field_dict["properties"] = properties 57 58 return field_dict 59 60 @classmethod 61 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 62 """@private""" 63 from ..models.component_properties_file import ComponentPropertiesFile 64 from ..models.track import Track 65 66 d = src_dict.copy() 67 id = d.pop("id") 68 69 tracks = [] 70 _tracks = d.pop("tracks") 71 for tracks_item_data in _tracks: 72 tracks_item = Track.from_dict(tracks_item_data) 73 74 tracks.append(tracks_item) 75 76 type = d.pop("type") 77 78 _properties = d.pop("properties", UNSET) 79 properties: Union[Unset, ComponentPropertiesFile] 80 if isinstance(_properties, Unset): 81 properties = UNSET 82 else: 83 properties = ComponentPropertiesFile.from_dict(_properties) 84 85 component_file = cls( 86 id=id, 87 tracks=tracks, 88 type=type, 89 properties=properties, 90 ) 91 92 component_file.additional_properties = d 93 return component_file 94 95 @property 96 def additional_keys(self) -> List[str]: 97 """@private""" 98 return list(self.additional_properties.keys()) 99 100 def __getitem__(self, key: str) -> Any: 101 return self.additional_properties[key] 102 103 def __setitem__(self, key: str, value: Any) -> None: 104 self.additional_properties[key] = value 105 106 def __delitem__(self, key: str) -> None: 107 del self.additional_properties[key] 108 109 def __contains__(self, key: str) -> bool: 110 return key in self.additional_properties
Describes the File component
2def __init__(self, id, tracks, type, properties=attr_dict['properties'].default): 3 self.id = id 4 self.tracks = tracks 5 self.type = type 6 self.properties = properties 7 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentFile.
Properties specific to the File component
15@_attrs_define 16class ComponentRTSP: 17 """Describes the RTSP component""" 18 19 id: str 20 """Assigned component ID""" 21 properties: "ComponentPropertiesRTSP" 22 """Properties specific to the RTSP component""" 23 tracks: List["Track"] 24 """List of all component's tracks""" 25 type: str 26 """Component type""" 27 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 28 """@private""" 29 30 def to_dict(self) -> Dict[str, Any]: 31 """@private""" 32 id = self.id 33 properties = self.properties.to_dict() 34 35 tracks = [] 36 for tracks_item_data in self.tracks: 37 tracks_item = tracks_item_data.to_dict() 38 39 tracks.append(tracks_item) 40 41 type = self.type 42 43 field_dict: Dict[str, Any] = {} 44 field_dict.update(self.additional_properties) 45 field_dict.update( 46 { 47 "id": id, 48 "properties": properties, 49 "tracks": tracks, 50 "type": type, 51 } 52 ) 53 54 return field_dict 55 56 @classmethod 57 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 58 """@private""" 59 from ..models.component_properties_rtsp import ComponentPropertiesRTSP 60 from ..models.track import Track 61 62 d = src_dict.copy() 63 id = d.pop("id") 64 65 properties = ComponentPropertiesRTSP.from_dict(d.pop("properties")) 66 67 tracks = [] 68 _tracks = d.pop("tracks") 69 for tracks_item_data in _tracks: 70 tracks_item = Track.from_dict(tracks_item_data) 71 72 tracks.append(tracks_item) 73 74 type = d.pop("type") 75 76 component_rtsp = cls( 77 id=id, 78 properties=properties, 79 tracks=tracks, 80 type=type, 81 ) 82 83 component_rtsp.additional_properties = d 84 return component_rtsp 85 86 @property 87 def additional_keys(self) -> List[str]: 88 """@private""" 89 return list(self.additional_properties.keys()) 90 91 def __getitem__(self, key: str) -> Any: 92 return self.additional_properties[key] 93 94 def __setitem__(self, key: str, value: Any) -> None: 95 self.additional_properties[key] = value 96 97 def __delitem__(self, key: str) -> None: 98 del self.additional_properties[key] 99 100 def __contains__(self, key: str) -> bool: 101 return key in self.additional_properties
Describes the RTSP component
2def __init__(self, id, properties, tracks, type): 3 self.id = id 4 self.properties = properties 5 self.tracks = tracks 6 self.type = type 7 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentRTSP.
12@_attrs_define 13class ComponentOptionsRTSP: 14 """Options specific to the RTSP component""" 15 16 source_uri: str 17 """URI of RTSP source stream""" 18 keep_alive_interval: Union[Unset, int] = 15000 19 """Interval (in ms) in which keep-alive RTSP messages will be sent to the remote stream source""" 20 pierce_nat: Union[Unset, bool] = True 21 """Whether to attempt to create client-side NAT binding by sending an empty datagram from client to source, after the completion of RTSP setup""" 22 reconnect_delay: Union[Unset, int] = 15000 23 """Delay (in ms) between successive reconnect attempts""" 24 rtp_port: Union[Unset, int] = 20000 25 """Local port RTP stream will be received at""" 26 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 27 """@private""" 28 29 def to_dict(self) -> Dict[str, Any]: 30 """@private""" 31 source_uri = self.source_uri 32 keep_alive_interval = self.keep_alive_interval 33 pierce_nat = self.pierce_nat 34 reconnect_delay = self.reconnect_delay 35 rtp_port = self.rtp_port 36 37 field_dict: Dict[str, Any] = {} 38 field_dict.update(self.additional_properties) 39 field_dict.update( 40 { 41 "sourceUri": source_uri, 42 } 43 ) 44 if keep_alive_interval is not UNSET: 45 field_dict["keepAliveInterval"] = keep_alive_interval 46 if pierce_nat is not UNSET: 47 field_dict["pierceNat"] = pierce_nat 48 if reconnect_delay is not UNSET: 49 field_dict["reconnectDelay"] = reconnect_delay 50 if rtp_port is not UNSET: 51 field_dict["rtpPort"] = rtp_port 52 53 return field_dict 54 55 @classmethod 56 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 57 """@private""" 58 d = src_dict.copy() 59 source_uri = d.pop("sourceUri") 60 61 keep_alive_interval = d.pop("keepAliveInterval", UNSET) 62 63 pierce_nat = d.pop("pierceNat", UNSET) 64 65 reconnect_delay = d.pop("reconnectDelay", UNSET) 66 67 rtp_port = d.pop("rtpPort", UNSET) 68 69 component_options_rtsp = cls( 70 source_uri=source_uri, 71 keep_alive_interval=keep_alive_interval, 72 pierce_nat=pierce_nat, 73 reconnect_delay=reconnect_delay, 74 rtp_port=rtp_port, 75 ) 76 77 component_options_rtsp.additional_properties = d 78 return component_options_rtsp 79 80 @property 81 def additional_keys(self) -> List[str]: 82 """@private""" 83 return list(self.additional_properties.keys()) 84 85 def __getitem__(self, key: str) -> Any: 86 return self.additional_properties[key] 87 88 def __setitem__(self, key: str, value: Any) -> None: 89 self.additional_properties[key] = value 90 91 def __delitem__(self, key: str) -> None: 92 del self.additional_properties[key] 93 94 def __contains__(self, key: str) -> bool: 95 return key in self.additional_properties
Options specific to the RTSP component
2def __init__(self, source_uri, keep_alive_interval=attr_dict['keep_alive_interval'].default, pierce_nat=attr_dict['pierce_nat'].default, reconnect_delay=attr_dict['reconnect_delay'].default, rtp_port=attr_dict['rtp_port'].default): 3 self.source_uri = source_uri 4 self.keep_alive_interval = keep_alive_interval 5 self.pierce_nat = pierce_nat 6 self.reconnect_delay = reconnect_delay 7 self.rtp_port = rtp_port 8 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentOptionsRTSP.
Interval (in ms) in which keep-alive RTSP messages will be sent to the remote stream source
Whether to attempt to create client-side NAT binding by sending an empty datagram from client to source, after the completion of RTSP setup
10@_attrs_define 11class ComponentPropertiesRTSP: 12 """Properties specific to the RTSP component""" 13 14 keep_alive_interval: int 15 """Interval (in ms) in which keep-alive RTSP messages will be sent to the remote stream source""" 16 pierce_nat: bool 17 """Whether to attempt to create client-side NAT binding by sending an empty datagram from client to source, after the completion of RTSP setup""" 18 reconnect_delay: int 19 """Delay (in ms) between successive reconnect attempts""" 20 rtp_port: int 21 """Local port RTP stream will be received at""" 22 source_uri: str 23 """URI of RTSP source stream""" 24 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 25 """@private""" 26 27 def to_dict(self) -> Dict[str, Any]: 28 """@private""" 29 keep_alive_interval = self.keep_alive_interval 30 pierce_nat = self.pierce_nat 31 reconnect_delay = self.reconnect_delay 32 rtp_port = self.rtp_port 33 source_uri = self.source_uri 34 35 field_dict: Dict[str, Any] = {} 36 field_dict.update(self.additional_properties) 37 field_dict.update( 38 { 39 "keepAliveInterval": keep_alive_interval, 40 "pierceNat": pierce_nat, 41 "reconnectDelay": reconnect_delay, 42 "rtpPort": rtp_port, 43 "sourceUri": source_uri, 44 } 45 ) 46 47 return field_dict 48 49 @classmethod 50 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 51 """@private""" 52 d = src_dict.copy() 53 keep_alive_interval = d.pop("keepAliveInterval") 54 55 pierce_nat = d.pop("pierceNat") 56 57 reconnect_delay = d.pop("reconnectDelay") 58 59 rtp_port = d.pop("rtpPort") 60 61 source_uri = d.pop("sourceUri") 62 63 component_properties_rtsp = cls( 64 keep_alive_interval=keep_alive_interval, 65 pierce_nat=pierce_nat, 66 reconnect_delay=reconnect_delay, 67 rtp_port=rtp_port, 68 source_uri=source_uri, 69 ) 70 71 component_properties_rtsp.additional_properties = d 72 return component_properties_rtsp 73 74 @property 75 def additional_keys(self) -> List[str]: 76 """@private""" 77 return list(self.additional_properties.keys()) 78 79 def __getitem__(self, key: str) -> Any: 80 return self.additional_properties[key] 81 82 def __setitem__(self, key: str, value: Any) -> None: 83 self.additional_properties[key] = value 84 85 def __delitem__(self, key: str) -> None: 86 del self.additional_properties[key] 87 88 def __contains__(self, key: str) -> bool: 89 return key in self.additional_properties
Properties specific to the RTSP component
2def __init__(self, keep_alive_interval, pierce_nat, reconnect_delay, rtp_port, source_uri): 3 self.keep_alive_interval = keep_alive_interval 4 self.pierce_nat = pierce_nat 5 self.reconnect_delay = reconnect_delay 6 self.rtp_port = rtp_port 7 self.source_uri = source_uri 8 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentPropertiesRTSP.
Interval (in ms) in which keep-alive RTSP messages will be sent to the remote stream source
12@_attrs_define 13class ComponentOptionsFile: 14 """Options specific to the File component""" 15 16 file_path: str 17 """Path to track file. Must be either OPUS encapsulated in Ogg or raw h264""" 18 framerate: Union[Unset, None, int] = UNSET 19 """Framerate of video in a file. It is only valid for video track""" 20 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 21 """@private""" 22 23 def to_dict(self) -> Dict[str, Any]: 24 """@private""" 25 file_path = self.file_path 26 framerate = self.framerate 27 28 field_dict: Dict[str, Any] = {} 29 field_dict.update(self.additional_properties) 30 field_dict.update( 31 { 32 "filePath": file_path, 33 } 34 ) 35 if framerate is not UNSET: 36 field_dict["framerate"] = framerate 37 38 return field_dict 39 40 @classmethod 41 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 42 """@private""" 43 d = src_dict.copy() 44 file_path = d.pop("filePath") 45 46 framerate = d.pop("framerate", UNSET) 47 48 component_options_file = cls( 49 file_path=file_path, 50 framerate=framerate, 51 ) 52 53 component_options_file.additional_properties = d 54 return component_options_file 55 56 @property 57 def additional_keys(self) -> List[str]: 58 """@private""" 59 return list(self.additional_properties.keys()) 60 61 def __getitem__(self, key: str) -> Any: 62 return self.additional_properties[key] 63 64 def __setitem__(self, key: str, value: Any) -> None: 65 self.additional_properties[key] = value 66 67 def __delitem__(self, key: str) -> None: 68 del self.additional_properties[key] 69 70 def __contains__(self, key: str) -> bool: 71 return key in self.additional_properties
Options specific to the File component
2def __init__(self, file_path, framerate=attr_dict['framerate'].default): 3 self.file_path = file_path 4 self.framerate = framerate 5 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentOptionsFile.
10@_attrs_define 11class ComponentPropertiesFile: 12 """Properties specific to the File component""" 13 14 file_path: str 15 """Relative path to track file. Must be either OPUS encapsulated in Ogg or raw h264""" 16 framerate: Optional[int] 17 """Framerate of video in a file. It is only valid for video track""" 18 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 19 """@private""" 20 21 def to_dict(self) -> Dict[str, Any]: 22 """@private""" 23 file_path = self.file_path 24 framerate = self.framerate 25 26 field_dict: Dict[str, Any] = {} 27 field_dict.update(self.additional_properties) 28 field_dict.update( 29 { 30 "filePath": file_path, 31 "framerate": framerate, 32 } 33 ) 34 35 return field_dict 36 37 @classmethod 38 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 39 """@private""" 40 d = src_dict.copy() 41 file_path = d.pop("filePath") 42 43 framerate = d.pop("framerate") 44 45 component_properties_file = cls( 46 file_path=file_path, 47 framerate=framerate, 48 ) 49 50 component_properties_file.additional_properties = d 51 return component_properties_file 52 53 @property 54 def additional_keys(self) -> List[str]: 55 """@private""" 56 return list(self.additional_properties.keys()) 57 58 def __getitem__(self, key: str) -> Any: 59 return self.additional_properties[key] 60 61 def __setitem__(self, key: str, value: Any) -> None: 62 self.additional_properties[key] = value 63 64 def __delitem__(self, key: str) -> None: 65 del self.additional_properties[key] 66 67 def __contains__(self, key: str) -> bool: 68 return key in self.additional_properties
Properties specific to the File component
10@_attrs_define 11class SIPCredentials: 12 """Credentials used to authorize in SIP Provider service""" 13 14 address: str 15 """SIP provider address. Can be in the form of FQDN (my-sip-registrar.net) or IPv4 (1.2.3.4). Port can be specified e.g: 5.6.7.8:9999. If not given, the default SIP port `5060` will be assumed""" 16 password: str 17 """Password in SIP service provider""" 18 username: str 19 """Username in SIP service provider""" 20 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 21 """@private""" 22 23 def to_dict(self) -> Dict[str, Any]: 24 """@private""" 25 address = self.address 26 password = self.password 27 username = self.username 28 29 field_dict: Dict[str, Any] = {} 30 field_dict.update(self.additional_properties) 31 field_dict.update( 32 { 33 "address": address, 34 "password": password, 35 "username": username, 36 } 37 ) 38 39 return field_dict 40 41 @classmethod 42 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 43 """@private""" 44 d = src_dict.copy() 45 address = d.pop("address") 46 47 password = d.pop("password") 48 49 username = d.pop("username") 50 51 sip_credentials = cls( 52 address=address, 53 password=password, 54 username=username, 55 ) 56 57 sip_credentials.additional_properties = d 58 return sip_credentials 59 60 @property 61 def additional_keys(self) -> List[str]: 62 """@private""" 63 return list(self.additional_properties.keys()) 64 65 def __getitem__(self, key: str) -> Any: 66 return self.additional_properties[key] 67 68 def __setitem__(self, key: str, value: Any) -> None: 69 self.additional_properties[key] = value 70 71 def __delitem__(self, key: str) -> None: 72 del self.additional_properties[key] 73 74 def __contains__(self, key: str) -> bool: 75 return key in self.additional_properties
Credentials used to authorize in SIP Provider service
2def __init__(self, address, password, username): 3 self.address = address 4 self.password = password 5 self.username = username 6 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class SIPCredentials.
15@_attrs_define 16class ComponentRecording: 17 """Describes the Recording component""" 18 19 id: str 20 """Assigned component ID""" 21 properties: "ComponentPropertiesRecording" 22 """Properties specific to the Recording component""" 23 tracks: List["Track"] 24 """List of all component's tracks""" 25 type: str 26 """Component type""" 27 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 28 """@private""" 29 30 def to_dict(self) -> Dict[str, Any]: 31 """@private""" 32 id = self.id 33 properties = self.properties.to_dict() 34 35 tracks = [] 36 for tracks_item_data in self.tracks: 37 tracks_item = tracks_item_data.to_dict() 38 39 tracks.append(tracks_item) 40 41 type = self.type 42 43 field_dict: Dict[str, Any] = {} 44 field_dict.update(self.additional_properties) 45 field_dict.update( 46 { 47 "id": id, 48 "properties": properties, 49 "tracks": tracks, 50 "type": type, 51 } 52 ) 53 54 return field_dict 55 56 @classmethod 57 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 58 """@private""" 59 from ..models.component_properties_recording import ComponentPropertiesRecording 60 from ..models.track import Track 61 62 d = src_dict.copy() 63 id = d.pop("id") 64 65 properties = ComponentPropertiesRecording.from_dict(d.pop("properties")) 66 67 tracks = [] 68 _tracks = d.pop("tracks") 69 for tracks_item_data in _tracks: 70 tracks_item = Track.from_dict(tracks_item_data) 71 72 tracks.append(tracks_item) 73 74 type = d.pop("type") 75 76 component_recording = cls( 77 id=id, 78 properties=properties, 79 tracks=tracks, 80 type=type, 81 ) 82 83 component_recording.additional_properties = d 84 return component_recording 85 86 @property 87 def additional_keys(self) -> List[str]: 88 """@private""" 89 return list(self.additional_properties.keys()) 90 91 def __getitem__(self, key: str) -> Any: 92 return self.additional_properties[key] 93 94 def __setitem__(self, key: str, value: Any) -> None: 95 self.additional_properties[key] = value 96 97 def __delitem__(self, key: str) -> None: 98 del self.additional_properties[key] 99 100 def __contains__(self, key: str) -> bool: 101 return key in self.additional_properties
Describes the Recording component
2def __init__(self, id, properties, tracks, type): 3 self.id = id 4 self.properties = properties 5 self.tracks = tracks 6 self.type = type 7 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentRecording.
19@_attrs_define 20class ComponentOptionsRecording: 21 """Options specific to the Recording component""" 22 23 credentials: Union[Unset, None, "S3Credentials"] = UNSET 24 """An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided""" 25 path_prefix: Union[Unset, None, str] = UNSET 26 """Path prefix under which all recording are stored""" 27 subscribe_mode: Union[ 28 Unset, ComponentOptionsRecordingSubscribeMode 29 ] = ComponentOptionsRecordingSubscribeMode.AUTO 30 """Whether the Recording component should subscribe to tracks automatically or manually.""" 31 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 32 """@private""" 33 34 def to_dict(self) -> Dict[str, Any]: 35 """@private""" 36 credentials: Union[Unset, None, Dict[str, Any]] = UNSET 37 if not isinstance(self.credentials, Unset): 38 credentials = self.credentials.to_dict() if self.credentials else None 39 40 path_prefix = self.path_prefix 41 subscribe_mode: Union[Unset, str] = UNSET 42 if not isinstance(self.subscribe_mode, Unset): 43 subscribe_mode = self.subscribe_mode.value 44 45 field_dict: Dict[str, Any] = {} 46 field_dict.update(self.additional_properties) 47 field_dict.update({}) 48 if credentials is not UNSET: 49 field_dict["credentials"] = credentials 50 if path_prefix is not UNSET: 51 field_dict["pathPrefix"] = path_prefix 52 if subscribe_mode is not UNSET: 53 field_dict["subscribeMode"] = subscribe_mode 54 55 return field_dict 56 57 @classmethod 58 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 59 """@private""" 60 from ..models.s3_credentials import S3Credentials 61 62 d = src_dict.copy() 63 _credentials = d.pop("credentials", UNSET) 64 credentials: Union[Unset, None, S3Credentials] 65 if _credentials is None: 66 credentials = None 67 elif isinstance(_credentials, Unset): 68 credentials = UNSET 69 else: 70 credentials = S3Credentials.from_dict(_credentials) 71 72 path_prefix = d.pop("pathPrefix", UNSET) 73 74 _subscribe_mode = d.pop("subscribeMode", UNSET) 75 subscribe_mode: Union[Unset, ComponentOptionsRecordingSubscribeMode] 76 if isinstance(_subscribe_mode, Unset): 77 subscribe_mode = UNSET 78 else: 79 subscribe_mode = ComponentOptionsRecordingSubscribeMode(_subscribe_mode) 80 81 component_options_recording = cls( 82 credentials=credentials, 83 path_prefix=path_prefix, 84 subscribe_mode=subscribe_mode, 85 ) 86 87 component_options_recording.additional_properties = d 88 return component_options_recording 89 90 @property 91 def additional_keys(self) -> List[str]: 92 """@private""" 93 return list(self.additional_properties.keys()) 94 95 def __getitem__(self, key: str) -> Any: 96 return self.additional_properties[key] 97 98 def __setitem__(self, key: str, value: Any) -> None: 99 self.additional_properties[key] = value 100 101 def __delitem__(self, key: str) -> None: 102 del self.additional_properties[key] 103 104 def __contains__(self, key: str) -> bool: 105 return key in self.additional_properties
Options specific to the Recording component
2def __init__(self, credentials=attr_dict['credentials'].default, path_prefix=attr_dict['path_prefix'].default, subscribe_mode=attr_dict['subscribe_mode'].default): 3 self.credentials = credentials 4 self.path_prefix = path_prefix 5 self.subscribe_mode = subscribe_mode 6 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentOptionsRecording.
An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided
Path prefix under which all recording are stored
Whether the Recording component should subscribe to tracks automatically or manually.
5class ComponentOptionsRecordingSubscribeMode(str, Enum): 6 """Whether the Recording component should subscribe to tracks automatically or manually.""" 7 8 AUTO = "auto" 9 MANUAL = "manual" 10 11 def __str__(self) -> str: 12 return str(self.value)
Whether the Recording component should subscribe to tracks automatically or manually.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
14@_attrs_define 15class ComponentPropertiesRecording: 16 """Properties specific to the Recording component""" 17 18 subscribe_mode: ComponentPropertiesRecordingSubscribeMode 19 """Whether the Recording component should subscribe to tracks automatically or manually""" 20 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 21 """@private""" 22 23 def to_dict(self) -> Dict[str, Any]: 24 """@private""" 25 subscribe_mode = self.subscribe_mode.value 26 27 field_dict: Dict[str, Any] = {} 28 field_dict.update(self.additional_properties) 29 field_dict.update( 30 { 31 "subscribeMode": subscribe_mode, 32 } 33 ) 34 35 return field_dict 36 37 @classmethod 38 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 39 """@private""" 40 d = src_dict.copy() 41 subscribe_mode = ComponentPropertiesRecordingSubscribeMode( 42 d.pop("subscribeMode") 43 ) 44 45 component_properties_recording = cls( 46 subscribe_mode=subscribe_mode, 47 ) 48 49 component_properties_recording.additional_properties = d 50 return component_properties_recording 51 52 @property 53 def additional_keys(self) -> List[str]: 54 """@private""" 55 return list(self.additional_properties.keys()) 56 57 def __getitem__(self, key: str) -> Any: 58 return self.additional_properties[key] 59 60 def __setitem__(self, key: str, value: Any) -> None: 61 self.additional_properties[key] = value 62 63 def __delitem__(self, key: str) -> None: 64 del self.additional_properties[key] 65 66 def __contains__(self, key: str) -> bool: 67 return key in self.additional_properties
Properties specific to the Recording component
2def __init__(self, subscribe_mode): 3 self.subscribe_mode = subscribe_mode 4 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class ComponentPropertiesRecording.
Whether the Recording component should subscribe to tracks automatically or manually
5class ComponentPropertiesRecordingSubscribeMode(str, Enum): 6 """Whether the Recording component should subscribe to tracks automatically or manually""" 7 8 AUTO = "auto" 9 MANUAL = "manual" 10 11 def __str__(self) -> str: 12 return str(self.value)
Whether the Recording component should subscribe to tracks automatically or manually
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
10@_attrs_define 11class S3Credentials: 12 """An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are 13 provided 14 15 """ 16 17 access_key_id: str 18 """An AWS access key identifier, linked to your AWS account.""" 19 bucket: str 20 """The name of the S3 bucket where your data will be stored.""" 21 region: str 22 """The AWS region where your bucket is located.""" 23 secret_access_key: str 24 """The secret key that is linked to the Access Key ID.""" 25 additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict) 26 """@private""" 27 28 def to_dict(self) -> Dict[str, Any]: 29 """@private""" 30 access_key_id = self.access_key_id 31 bucket = self.bucket 32 region = self.region 33 secret_access_key = self.secret_access_key 34 35 field_dict: Dict[str, Any] = {} 36 field_dict.update(self.additional_properties) 37 field_dict.update( 38 { 39 "accessKeyId": access_key_id, 40 "bucket": bucket, 41 "region": region, 42 "secretAccessKey": secret_access_key, 43 } 44 ) 45 46 return field_dict 47 48 @classmethod 49 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: 50 """@private""" 51 d = src_dict.copy() 52 access_key_id = d.pop("accessKeyId") 53 54 bucket = d.pop("bucket") 55 56 region = d.pop("region") 57 58 secret_access_key = d.pop("secretAccessKey") 59 60 s3_credentials = cls( 61 access_key_id=access_key_id, 62 bucket=bucket, 63 region=region, 64 secret_access_key=secret_access_key, 65 ) 66 67 s3_credentials.additional_properties = d 68 return s3_credentials 69 70 @property 71 def additional_keys(self) -> List[str]: 72 """@private""" 73 return list(self.additional_properties.keys()) 74 75 def __getitem__(self, key: str) -> Any: 76 return self.additional_properties[key] 77 78 def __setitem__(self, key: str, value: Any) -> None: 79 self.additional_properties[key] = value 80 81 def __delitem__(self, key: str) -> None: 82 del self.additional_properties[key] 83 84 def __contains__(self, key: str) -> bool: 85 return key in self.additional_properties
An AWS S3 credential that will be used to send HLS stream. The stream will only be uploaded if credentials are provided
2def __init__(self, access_key_id, bucket, region, secret_access_key): 3 self.access_key_id = access_key_id 4 self.bucket = bucket 5 self.region = region 6 self.secret_access_key = secret_access_key 7 self.additional_properties = __attr_factory_additional_properties()
Method generated by attrs for class S3Credentials.