Skip to content

Commit 894d775

Browse files
authored
Add viewer token (#27)
* Add generating viewer token to API * Update openapi * Enable broadcasting rooms in fishjam container * Fix tests * Rename room type broadcaster to livestream
1 parent 8df875a commit 894d775

File tree

11 files changed

+129
-93
lines changed

11 files changed

+129
-93
lines changed

docker-compose-test.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ services:
1717
FJ_SECRET_KEY_BASE: "super-secret-key"
1818
FJ_SIP_IP: "127.0.0.1"
1919
FJ_COMPONENTS_USED: "rtsp file hls recording sip"
20+
FJ_BROADCASTING_ENABLED: "true"
21+
FJ_BROADCASTER_URL: "http://broadcaster:4000"
22+
FJ_BROADCASTER_TOKEN: "broadcaster_token"
23+
FJ_BROADCASTER_WHIP_TOKEN: "whip_token"
2024
ports:
2125
- "5002:5002"
2226
- "49999:49999"

examples/room_manager/room_service.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ def __find_or_create_room(
8181
options = RoomOptions(
8282
max_peers=self.config.max_peers,
8383
webhook_url=self.config.webhook_url,
84-
peerless_purge_timeout=self.config.peerless_purge_timeout,
8584
room_type=room_type.value if room_type else "full_feature",
8685
)
8786

fishjam/_openapi_client/api/viewer/generate_token.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from http import HTTPStatus
2-
from typing import Any, Dict, Optional, Union, cast
2+
from typing import Any, Dict, Optional, Union
33

44
import httpx
55

66
from ... import errors
77
from ...client import AuthenticatedClient, Client
88
from ...models.error import Error
9+
from ...models.viewer_token import ViewerToken
910
from ...types import Response
1011

1112

@@ -22,9 +23,10 @@ def _get_kwargs(
2223

2324
def _parse_response(
2425
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
25-
) -> Optional[Union[Error, str]]:
26+
) -> Optional[Union[Error, ViewerToken]]:
2627
if response.status_code == HTTPStatus.CREATED:
27-
response_201 = cast(str, response.json())
28+
response_201 = ViewerToken.from_dict(response.json())
29+
2830
return response_201
2931
if response.status_code == HTTPStatus.BAD_REQUEST:
3032
response_400 = Error.from_dict(response.json())
@@ -50,7 +52,7 @@ def _parse_response(
5052

5153
def _build_response(
5254
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
53-
) -> Response[Union[Error, str]]:
55+
) -> Response[Union[Error, ViewerToken]]:
5456
return Response(
5557
status_code=HTTPStatus(response.status_code),
5658
content=response.content,
@@ -63,8 +65,8 @@ def sync_detailed(
6365
room_id: str,
6466
*,
6567
client: Union[AuthenticatedClient, Client],
66-
) -> Response[Union[Error, str]]:
67-
"""Generate token for single viewer
68+
) -> Response[Union[Error, ViewerToken]]:
69+
"""Generate single broadcaster access token
6870
6971
Args:
7072
room_id (str):
@@ -74,7 +76,7 @@ def sync_detailed(
7476
httpx.TimeoutException: If the request takes longer than Client.timeout.
7577
7678
Returns:
77-
Response[Union[Error, str]]
79+
Response[Union[Error, ViewerToken]]
7880
"""
7981

8082
kwargs = _get_kwargs(
@@ -92,8 +94,8 @@ def sync(
9294
room_id: str,
9395
*,
9496
client: Union[AuthenticatedClient, Client],
95-
) -> Optional[Union[Error, str]]:
96-
"""Generate token for single viewer
97+
) -> Optional[Union[Error, ViewerToken]]:
98+
"""Generate single broadcaster access token
9799
98100
Args:
99101
room_id (str):
@@ -103,7 +105,7 @@ def sync(
103105
httpx.TimeoutException: If the request takes longer than Client.timeout.
104106
105107
Returns:
106-
Union[Error, str]
108+
Union[Error, ViewerToken]
107109
"""
108110

109111
return sync_detailed(
@@ -116,8 +118,8 @@ async def asyncio_detailed(
116118
room_id: str,
117119
*,
118120
client: Union[AuthenticatedClient, Client],
119-
) -> Response[Union[Error, str]]:
120-
"""Generate token for single viewer
121+
) -> Response[Union[Error, ViewerToken]]:
122+
"""Generate single broadcaster access token
121123
122124
Args:
123125
room_id (str):
@@ -127,7 +129,7 @@ async def asyncio_detailed(
127129
httpx.TimeoutException: If the request takes longer than Client.timeout.
128130
129131
Returns:
130-
Response[Union[Error, str]]
132+
Response[Union[Error, ViewerToken]]
131133
"""
132134

133135
kwargs = _get_kwargs(
@@ -143,8 +145,8 @@ async def asyncio(
143145
room_id: str,
144146
*,
145147
client: Union[AuthenticatedClient, Client],
146-
) -> Optional[Union[Error, str]]:
147-
"""Generate token for single viewer
148+
) -> Optional[Union[Error, ViewerToken]]:
149+
"""Generate single broadcaster access token
148150
149151
Args:
150152
room_id (str):
@@ -154,7 +156,7 @@ async def asyncio(
154156
httpx.TimeoutException: If the request takes longer than Client.timeout.
155157
156158
Returns:
157-
Union[Error, str]
159+
Union[Error, ViewerToken]
158160
"""
159161

160162
return (

fishjam/_openapi_client/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from .track_type import TrackType
6969
from .user import User
7070
from .user_listing_response import UserListingResponse
71+
from .viewer_token import ViewerToken
7172

7273
__all__ = (
7374
"AddComponentJsonBody",
@@ -130,4 +131,5 @@
130131
"TrackType",
131132
"User",
132133
"UserListingResponse",
134+
"ViewerToken",
133135
)

fishjam/_openapi_client/models/room_config.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ class RoomConfig:
1616

1717
max_peers: Union[Unset, None, int] = UNSET
1818
"""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."""
2319
room_type: Union[Unset, RoomConfigRoomType] = RoomConfigRoomType.FULL_FEATURE
2420
"""The use-case of the room. If not provided, this defaults to full_feature."""
2521
video_codec: Union[Unset, None, RoomConfigVideoCodec] = UNSET
@@ -32,8 +28,6 @@ class RoomConfig:
3228
def to_dict(self) -> Dict[str, Any]:
3329
"""@private"""
3430
max_peers = self.max_peers
35-
peer_disconnected_timeout = self.peer_disconnected_timeout
36-
peerless_purge_timeout = self.peerless_purge_timeout
3731
room_type: Union[Unset, str] = UNSET
3832
if not isinstance(self.room_type, Unset):
3933
room_type = self.room_type.value
@@ -49,10 +43,6 @@ def to_dict(self) -> Dict[str, Any]:
4943
field_dict.update({})
5044
if max_peers is not UNSET:
5145
field_dict["maxPeers"] = max_peers
52-
if peer_disconnected_timeout is not UNSET:
53-
field_dict["peerDisconnectedTimeout"] = peer_disconnected_timeout
54-
if peerless_purge_timeout is not UNSET:
55-
field_dict["peerlessPurgeTimeout"] = peerless_purge_timeout
5646
if room_type is not UNSET:
5747
field_dict["roomType"] = room_type
5848
if video_codec is not UNSET:
@@ -68,10 +58,6 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
6858
d = src_dict.copy()
6959
max_peers = d.pop("maxPeers", UNSET)
7060

71-
peer_disconnected_timeout = d.pop("peerDisconnectedTimeout", UNSET)
72-
73-
peerless_purge_timeout = d.pop("peerlessPurgeTimeout", UNSET)
74-
7561
_room_type = d.pop("roomType", UNSET)
7662
room_type: Union[Unset, RoomConfigRoomType]
7763
if isinstance(_room_type, Unset):
@@ -92,8 +78,6 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
9278

9379
room_config = cls(
9480
max_peers=max_peers,
95-
peer_disconnected_timeout=peer_disconnected_timeout,
96-
peerless_purge_timeout=peerless_purge_timeout,
9781
room_type=room_type,
9882
video_codec=video_codec,
9983
webhook_url=webhook_url,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from typing import Any, Dict, List, Type, TypeVar
2+
3+
from attrs import define as _attrs_define
4+
from attrs import field as _attrs_field
5+
6+
T = TypeVar("T", bound="ViewerToken")
7+
8+
9+
@_attrs_define
10+
class ViewerToken:
11+
"""Token for authorizing broadcaster viewer connection"""
12+
13+
token: str
14+
"""None"""
15+
additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict)
16+
"""@private"""
17+
18+
def to_dict(self) -> Dict[str, Any]:
19+
"""@private"""
20+
token = self.token
21+
22+
field_dict: Dict[str, Any] = {}
23+
field_dict.update(self.additional_properties)
24+
field_dict.update(
25+
{
26+
"token": token,
27+
}
28+
)
29+
30+
return field_dict
31+
32+
@classmethod
33+
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
34+
"""@private"""
35+
d = src_dict.copy()
36+
token = d.pop("token")
37+
38+
viewer_token = cls(
39+
token=token,
40+
)
41+
42+
viewer_token.additional_properties = d
43+
return viewer_token
44+
45+
@property
46+
def additional_keys(self) -> List[str]:
47+
"""@private"""
48+
return list(self.additional_properties.keys())
49+
50+
def __getitem__(self, key: str) -> Any:
51+
return self.additional_properties[key]
52+
53+
def __setitem__(self, key: str, value: Any) -> None:
54+
self.additional_properties[key] = value
55+
56+
def __delitem__(self, key: str) -> None:
57+
del self.additional_properties[key]
58+
59+
def __contains__(self, key: str) -> bool:
60+
return key in self.additional_properties

fishjam/api/_fishjam_client.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from fishjam._openapi_client.api.room import get_all_rooms as room_get_all_rooms
1313
from fishjam._openapi_client.api.room import get_room as room_get_room
1414
from fishjam._openapi_client.api.room import refresh_token as room_refresh_token
15+
from fishjam._openapi_client.api.viewer import generate_token as viewer_generate_token
1516
from fishjam._openapi_client.models import (
1617
AddPeerJsonBody,
1718
Peer,
@@ -23,6 +24,7 @@
2324
RoomCreateDetailsResponse,
2425
RoomDetailsResponse,
2526
RoomsListingResponse,
27+
ViewerToken,
2628
)
2729
from fishjam._openapi_client.models.peer_options_web_rtc_metadata import (
2830
PeerOptionsWebRTCMetadata,
@@ -49,21 +51,13 @@ class RoomOptions:
4951

5052
max_peers: int | None = None
5153
"""Maximum amount of peers allowed into the room"""
52-
peer_disconnected_timeout: int | None = None
53-
"""
54-
Duration (in seconds) after which the peer will be removed if it is disconnected.
55-
If not provided, this feature is disabled.
56-
"""
57-
peerless_purge_timeout: int | None = None
58-
"""
59-
Duration (in seconds) after which the room will be removed
60-
if no peers are connected. If not provided, this feature is disabled.
61-
"""
6254
video_codec: Literal["h264", "vp8"] | None = None
6355
"""Enforces video codec for each peer in the room"""
6456
webhook_url: str | None = None
6557
"""URL where Fishjam notifications will be sent"""
66-
room_type: Literal["full_feature", "audio_only", "broadcaster"] = "full_feature"
58+
room_type: Literal[
59+
"full_feature", "audio_only", "broadcaster", "livestream"
60+
] = "full_feature"
6761
"""The use-case of the room. If not provided, this defaults to full_feature."""
6862

6963

@@ -124,10 +118,11 @@ def create_room(self, options: RoomOptions | None = None) -> Room:
124118
if options.video_codec:
125119
codec = RoomConfigVideoCodec(options.video_codec)
126120

121+
if options.room_type == "livestream":
122+
options.room_type = "broadcaster"
123+
127124
config = RoomConfig(
128125
max_peers=options.max_peers,
129-
peer_disconnected_timeout=options.peer_disconnected_timeout,
130-
peerless_purge_timeout=options.peerless_purge_timeout,
131126
video_codec=codec,
132127
webhook_url=options.webhook_url,
133128
room_type=RoomConfigRoomType(options.room_type),
@@ -177,6 +172,15 @@ def refresh_peer_token(self, room_id: str, peer_id: str) -> str:
177172

178173
return response.data.token
179174

175+
def create_livestream_viewer_token(self, room_id: str) -> str:
176+
"""Generates viewer token for livestream rooms"""
177+
178+
response = cast(
179+
ViewerToken, self._request(viewer_generate_token, room_id=room_id)
180+
)
181+
182+
return response.token
183+
180184
def __parse_peer_metadata(self, metadata: dict | None) -> PeerOptionsWebRTCMetadata:
181185
peer_metadata = PeerOptionsWebRTCMetadata()
182186

poetry_scripts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def run_tests():
3535

3636

3737
def run_local_test():
38-
check_exit_code('poetry run pytest -m "not file_component_sources"')
38+
check_exit_code('poetry run pytest -m "not file_component_sources" -vv')
3939

4040

4141
def run_formatter():

tests/test_notifier.py

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -154,45 +154,6 @@ async def test_peer_connected_disconnected(
154154
for event in event_checks:
155155
self.assert_event(event)
156156

157-
@pytest.mark.asyncio
158-
async def test_peer_connected_disconnected_deleted(
159-
self, room_api: FishjamClient, notifier: FishjamNotifier
160-
):
161-
event_checks = [
162-
ServerMessageRoomCreated,
163-
ServerMessagePeerAdded,
164-
ServerMessagePeerConnected,
165-
ServerMessagePeerDisconnected,
166-
ServerMessagePeerDeleted,
167-
ServerMessageRoomDeleted,
168-
]
169-
170-
assert_task = asyncio.create_task(assert_events(notifier, event_checks.copy()))
171-
172-
notifier_task = asyncio.create_task(notifier.connect())
173-
await notifier.wait_ready()
174-
175-
options = RoomOptions(
176-
webhook_url=WEBHOOK_URL,
177-
peerless_purge_timeout=2,
178-
peer_disconnected_timeout=1,
179-
)
180-
room = room_api.create_room(options=options)
181-
182-
_peer, token = room_api.create_peer(room.id)
183-
184-
peer_socket = PeerSocket(fishjam_url=FISHJAM_URL, auto_close=True)
185-
peer_task = asyncio.create_task(peer_socket.connect(token))
186-
187-
await peer_socket.wait_ready()
188-
189-
await assert_task
190-
await cancel(peer_task)
191-
await cancel(notifier_task)
192-
193-
for event in event_checks:
194-
self.assert_event(event)
195-
196157
@pytest.mark.asyncio
197158
async def test_peer_connected_room_deleted(
198159
self, room_api: FishjamClient, notifier: FishjamNotifier

0 commit comments

Comments
 (0)