Skip to content

Commit 9d81efe

Browse files
Rados13sgfn
andauthored
RTC-458 Extend docs (#31)
* Extend docs * Add mini_tutorial example and run examples in CI * Fix lint * Run firstly examples * Fixes * Update examples/mini_tutorial.py Co-authored-by: Jakub Pisarek <[email protected]> * Update jellyfish/api/_room_api.py Co-authored-by: Jakub Pisarek <[email protected]> * Update jellyfish/api/_room_api.py Co-authored-by: Jakub Pisarek <[email protected]> * Improve README --------- Co-authored-by: Jakub Pisarek <[email protected]>
1 parent 9ef121b commit 9d81efe

File tree

9 files changed

+194
-15
lines changed

9 files changed

+194
-15
lines changed

.circleci/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ jobs:
3838
steps:
3939
- checkout
4040
- run: docker compose -f docker-compose-test.yaml up test --exit-code-from test
41+
- run: docker compose -f docker-compose-test.yaml down
42+
- run: docker compose -f docker-compose-test.yaml up examples --exit-code-from examples
43+
4144

4245

4346
workflows:

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,29 @@ asyncio.run(test_notifier())
9595
# Received WebRTC metrics: ServerMessageMetricsReport(metrics='{}')
9696
```
9797

98+
#### Cluster of Jellyfishes
99+
100+
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.
101+
102+
```python
103+
room_api = RoomApi(server_address='localhost:5002')
104+
105+
# Create a room to trigger a server notification with h264 as a codec,
106+
# that allow to use HLS.
107+
address, room = room_api.create_room(video_codec="h264")
108+
109+
# Create new room api with returned jellyfish address as a room could be
110+
# created on a different jellyfish instance
111+
# (if you communicate with a cluster of jellyfishes)
112+
new_room_api = RoomApi(server_address=address)
113+
114+
# 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.
115+
_hls_component = new_room_api.add_component(
116+
room.id,
117+
ComponentOptionsHLS(subscribe_mode=ComponentOptionsHLSSubscribeMode.MANUAL),
118+
)
119+
```
120+
98121
## Testing
99122

100123
You can test the SDK by running

docker-compose-test.yaml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ services:
1313
timeout: 2s
1414
start_period: 30s
1515
environment:
16-
JF_HOST: "jellyfish"
16+
JF_HOST: "jellyfish:5002"
1717
JF_INTEGRATED_TURN_IP: "${INTEGRATED_TURN_IP:-127.0.0.1}"
1818
JF_INTEGRATED_TURN_LISTEN_IP: "0.0.0.0"
1919
JF_INTEGRATED_TURN_PORT_RANGE: "50000-50050"
@@ -43,3 +43,16 @@ services:
4343
depends_on:
4444
jellyfish:
4545
condition: service_healthy
46+
47+
examples:
48+
container_name: examples
49+
image: "cimg/python:${PYTHON_VERSION:-3.8}"
50+
command: sh -c "cd /app && \ poetry config virtualenvs.in-project false && \ poetry cache clear pypi --all && \ poetry install --no-ansi && \ poetry run examples"
51+
environment:
52+
DOCKER_TEST: "TRUE"
53+
CI_LIMIT: "10"
54+
volumes:
55+
- .:/app
56+
depends_on:
57+
jellyfish:
58+
condition: service_healthy

examples/mini_tutorial.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import asyncio
2+
import os
3+
4+
from jellyfish import (
5+
ComponentOptionsFile,
6+
ComponentOptionsHLS,
7+
ComponentOptionsHLSSubscribeMode,
8+
Notifier,
9+
RoomApi,
10+
)
11+
from jellyfish.events import (
12+
ServerMessageHlsPlayable,
13+
ServerMessageTrackAdded,
14+
ServerMessageTrackType,
15+
)
16+
17+
HOST = "jellyfish" if os.getenv("DOCKER_TEST") == "TRUE" else "localhost"
18+
SERVER_ADDRESS = f"{HOST}:5002"
19+
20+
21+
notifier = Notifier(server_address=SERVER_ADDRESS, server_api_token="development")
22+
23+
notifier_task = None
24+
25+
26+
@notifier.on_server_notification
27+
def handle_notification(server_notification):
28+
print(f"Received a notification: {server_notification}")
29+
30+
if isinstance(server_notification, ServerMessageTrackAdded):
31+
if server_notification.track.type == ServerMessageTrackType.TRACK_TYPE_AUDIO:
32+
print("New audio track has been added")
33+
elif server_notification.track.type == ServerMessageTrackType.TRACK_TYPE_VIDEO:
34+
print("New video track has been added")
35+
elif isinstance(server_notification, ServerMessageHlsPlayable):
36+
print("HLS stream is playable")
37+
notifier_task.cancel()
38+
39+
40+
@notifier.on_metrics
41+
def handle_metrics(metrics_report):
42+
pass
43+
44+
45+
async def test_notifier():
46+
global notifier_task
47+
notifier_task = asyncio.create_task(notifier.connect())
48+
49+
# Wait for notifier to be ready to receive messages
50+
await notifier.wait_ready()
51+
52+
room_api = RoomApi(server_address=SERVER_ADDRESS)
53+
54+
# Create a room to trigger a server notification with h264 as a codec,
55+
# that allow to use HLS.
56+
address, room = room_api.create_room(video_codec="h264")
57+
58+
# Create new room api with returned jellyfish address as a room could be
59+
# created on a different jellyfish instance
60+
# (if you communicate with a cluster of jellyfishes)
61+
room_api = RoomApi(server_address=address)
62+
63+
# Add HLS component with manual subscribe mode
64+
_hls_component = room_api.add_component(
65+
room.id,
66+
ComponentOptionsHLS(subscribe_mode=ComponentOptionsHLSSubscribeMode.MANUAL),
67+
)
68+
69+
# Add File Component
70+
file_component = room_api.add_component(room.id, ComponentOptionsFile("video.h264"))
71+
72+
# Subscribe on specific component
73+
room_api.hls_subscribe(room.id, [file_component.id])
74+
75+
try:
76+
await notifier_task
77+
except asyncio.CancelledError:
78+
print("Notifier task canceled, exiting")
79+
80+
81+
asyncio.run(test_notifier())

examples/room_api.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import os
2+
13
from jellyfish import ComponentOptionsHLS, PeerOptionsWebRTC, RoomApi
24

5+
HOST = "jellyfish" if os.getenv("DOCKER_TEST") == "TRUE" else "localhost"
6+
SERVER_ADDRESS = f"{HOST}:5002"
7+
38
# Create a room
4-
room_api = RoomApi(server_address="localhost:5002", server_api_token="development")
9+
room_api = RoomApi(server_address=SERVER_ADDRESS, server_api_token="development")
510

611
jellyfish_address, room = room_api.create_room(
712
video_codec="h264", webhook_url="http://localhost:5000/webhook"

examples/server_notifications.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
import asyncio
2+
import os
23

34
from jellyfish import Notifier, RoomApi
45
from jellyfish.events import ServerMessageTrackAdded, ServerMessageTrackType
56

6-
notifier = Notifier(server_address="localhost:5002", server_api_token="development")
7+
HOST = "jellyfish" if os.getenv("DOCKER_TEST") == "TRUE" else "localhost"
8+
SERVER_ADDRESS = f"{HOST}:5002"
9+
10+
notifier = Notifier(server_address=SERVER_ADDRESS, server_api_token="development")
11+
12+
notifier_task = None
13+
14+
LIMIT = os.getenv("CI_LIMIT", None)
15+
16+
if LIMIT is not None:
17+
LIMIT = int(LIMIT)
18+
19+
20+
counter = 0
721

822

923
@notifier.on_server_notification
@@ -20,19 +34,27 @@ def handle_notification(server_notification):
2034
@notifier.on_metrics
2135
def handle_metrics(metrics_report):
2236
print(f"Received WebRTC metrics: {metrics_report}")
37+
global counter
38+
if LIMIT and counter > LIMIT:
39+
notifier_task.cancel()
40+
counter += 1
2341

2442

2543
async def test_notifier():
44+
global notifier_task
2645
notifier_task = asyncio.create_task(notifier.connect())
2746

2847
# Wait for notifier to be ready to receive messages
2948
await notifier.wait_ready()
3049

3150
# Create a room to trigger a server notification
32-
room_api = RoomApi()
51+
room_api = RoomApi(server_address=SERVER_ADDRESS)
3352
room_api.create_room()
3453

35-
await notifier_task
54+
try:
55+
await notifier_task
56+
except asyncio.CancelledError:
57+
print("Notifier task canceled, exiting")
3658

3759

3860
asyncio.run(test_notifier())

jellyfish/api/_room_api.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
RoomApi used to manage rooms
33
"""
44

5-
from typing import Literal, Union
5+
from typing import List, Literal, Tuple, Union
66

77
from jellyfish._openapi_client.api.hls import subscribe_hls_to as hls_subscribe_hls_to
88
from jellyfish._openapi_client.api.room import add_component as room_add_component
@@ -62,7 +62,7 @@ def create_room(
6262
max_peers: int = None,
6363
video_codec: Literal["h264", "vp8"] = None,
6464
webhook_url: str = None,
65-
) -> (str, Room):
65+
) -> Tuple[str, Room]:
6666
"""
6767
Creates a new room
6868
@@ -104,14 +104,16 @@ def get_room(self, room_id: str) -> Room:
104104

105105
return self._request(room_get_room, room_id=room_id).data
106106

107-
def add_peer(self, room_id: str, options: PeerOptionsWebRTC) -> (str, Peer):
107+
def add_peer(self, room_id: str, options: PeerOptionsWebRTC) -> Tuple[str, Peer]:
108108
"""
109109
Creates peer in the room
110110
111111
Currently only `webrtc` peer is supported
112112
113113
Returns a tuple (`peer_token`, `Peer`) - the token needed by Peer
114-
to authenticate to Jellyfish and the new `Peer`
114+
to authenticate to Jellyfish and the new `Peer`.
115+
116+
The possible options to pass for peer are `PeerOptionsWebRTC`.
115117
"""
116118

117119
peer_type = "webrtc"
@@ -135,7 +137,14 @@ def add_component(
135137
ComponentOptionsSIP,
136138
],
137139
) -> Union[ComponentFile, ComponentHLS, ComponentRTSP, ComponentSIP]:
138-
"""Creates component in the room"""
140+
"""
141+
Creates component in the room.
142+
Currently there are 4 different components:
143+
* File Component for which the options are `ComponentOptionsFile`
144+
* HLS Component which options are `ComponentOptionsHLS`
145+
* RTSP Component which options are `ComponentOptionsRTSP`
146+
* SIP Component which options are `ComponentOptionsSIP`
147+
"""
139148

140149
if isinstance(options, ComponentOptionsFile):
141150
component_type = "file"
@@ -147,7 +156,7 @@ def add_component(
147156
component_type = "sip"
148157
else:
149158
raise ValueError(
150-
"options must be ComponentFile, ComponentOptionsHLS,"
159+
"options must be ComponentOptionsFile, ComponentOptionsHLS,"
151160
"ComponentOptionsRTSP or ComponentOptionsSIP"
152161
)
153162

@@ -162,7 +171,7 @@ def delete_component(self, room_id: str, component_id: str) -> None:
162171

163172
return self._request(room_delete_component, id=component_id, room_id=room_id)
164173

165-
def hls_subscribe(self, room_id: str, origins: [str]):
174+
def hls_subscribe(self, room_id: str, origins: List[str]):
166175
"""
167176
In order to subscribe to HLS peers/components,
168177
the HLS component should be initialized with the subscribe_mode set to manual.

poetry_scripts.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
import os
22
import shutil
3+
import subprocess
34
import sys
45
from pathlib import Path
56

67

78
def check_exit_code(command):
8-
command_exit_code = os.system(command)
9-
if command_exit_code != 0:
10-
sys.exit(command_exit_code >> 8)
9+
process = subprocess.Popen(
10+
command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
11+
)
12+
13+
while True:
14+
output = process.stdout.readline()
15+
if output == b"" and process.poll() is not None:
16+
break
17+
if output:
18+
print(str(output.strip(), "utf-8"))
19+
exit_code = process.poll()
20+
if exit_code != 0:
21+
sys.exit(exit_code)
1122

1223

1324
def run_tests():
@@ -20,6 +31,17 @@ def run_tests():
2031
check_exit_code("docker compose -f docker-compose-test.yaml down")
2132

2233

34+
def run_examples():
35+
print("Start examples")
36+
37+
examples = os.listdir("./examples")
38+
39+
for example in examples:
40+
check_exit_code(f"python ./examples/{example}")
41+
print(f"After example from file: {example}")
42+
print("All examples run without errors")
43+
44+
2345
def run_local_test():
2446
check_exit_code('poetry run pytest -m "not file_component_sources"')
2547

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ lint = "poetry_scripts:run_linter"
4646
fix_lint = "poetry_scripts:run_linter_fix"
4747
generate_docs = "poetry_scripts:generate_docs"
4848
update_client = "poetry_scripts:update_client"
49+
examples = "poetry_scripts:run_examples"
4950

5051
[tool.ruff]
5152
select = ["F", "I"]

0 commit comments

Comments
 (0)