Skip to content

Commit

Permalink
RTC-458 Extend docs (#31)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
Rados13 and sgfn authored Feb 21, 2024
1 parent 9ef121b commit 9d81efe
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
steps:
- checkout
- run: docker compose -f docker-compose-test.yaml up test --exit-code-from test
- run: docker compose -f docker-compose-test.yaml down
- run: docker compose -f docker-compose-test.yaml up examples --exit-code-from examples



workflows:
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,29 @@ asyncio.run(test_notifier())
# 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.

```python
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
Expand Down
15 changes: 14 additions & 1 deletion docker-compose-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
timeout: 2s
start_period: 30s
environment:
JF_HOST: "jellyfish"
JF_HOST: "jellyfish:5002"
JF_INTEGRATED_TURN_IP: "${INTEGRATED_TURN_IP:-127.0.0.1}"
JF_INTEGRATED_TURN_LISTEN_IP: "0.0.0.0"
JF_INTEGRATED_TURN_PORT_RANGE: "50000-50050"
Expand Down Expand Up @@ -43,3 +43,16 @@ services:
depends_on:
jellyfish:
condition: service_healthy

examples:
container_name: examples
image: "cimg/python:${PYTHON_VERSION:-3.8}"
command: sh -c "cd /app && \ poetry config virtualenvs.in-project false && \ poetry cache clear pypi --all && \ poetry install --no-ansi && \ poetry run examples"
environment:
DOCKER_TEST: "TRUE"
CI_LIMIT: "10"
volumes:
- .:/app
depends_on:
jellyfish:
condition: service_healthy
81 changes: 81 additions & 0 deletions examples/mini_tutorial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import asyncio
import os

from jellyfish import (
ComponentOptionsFile,
ComponentOptionsHLS,
ComponentOptionsHLSSubscribeMode,
Notifier,
RoomApi,
)
from jellyfish.events import (
ServerMessageHlsPlayable,
ServerMessageTrackAdded,
ServerMessageTrackType,
)

HOST = "jellyfish" if os.getenv("DOCKER_TEST") == "TRUE" else "localhost"
SERVER_ADDRESS = f"{HOST}:5002"


notifier = Notifier(server_address=SERVER_ADDRESS, server_api_token="development")

notifier_task = None


@notifier.on_server_notification
def handle_notification(server_notification):
print(f"Received a notification: {server_notification}")

if isinstance(server_notification, ServerMessageTrackAdded):
if server_notification.track.type == ServerMessageTrackType.TRACK_TYPE_AUDIO:
print("New audio track has been added")
elif server_notification.track.type == ServerMessageTrackType.TRACK_TYPE_VIDEO:
print("New video track has been added")
elif isinstance(server_notification, ServerMessageHlsPlayable):
print("HLS stream is playable")
notifier_task.cancel()


@notifier.on_metrics
def handle_metrics(metrics_report):
pass


async def test_notifier():
global notifier_task
notifier_task = asyncio.create_task(notifier.connect())

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

room_api = RoomApi(server_address=SERVER_ADDRESS)

# 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)
room_api = RoomApi(server_address=address)

# Add HLS component with manual subscribe mode
_hls_component = room_api.add_component(
room.id,
ComponentOptionsHLS(subscribe_mode=ComponentOptionsHLSSubscribeMode.MANUAL),
)

# Add File Component
file_component = room_api.add_component(room.id, ComponentOptionsFile("video.h264"))

# Subscribe on specific component
room_api.hls_subscribe(room.id, [file_component.id])

try:
await notifier_task
except asyncio.CancelledError:
print("Notifier task canceled, exiting")


asyncio.run(test_notifier())
7 changes: 6 additions & 1 deletion examples/room_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import os

from jellyfish import ComponentOptionsHLS, PeerOptionsWebRTC, RoomApi

HOST = "jellyfish" if os.getenv("DOCKER_TEST") == "TRUE" else "localhost"
SERVER_ADDRESS = f"{HOST}:5002"

# Create a room
room_api = RoomApi(server_address="localhost:5002", server_api_token="development")
room_api = RoomApi(server_address=SERVER_ADDRESS, server_api_token="development")

jellyfish_address, room = room_api.create_room(
video_codec="h264", webhook_url="http://localhost:5000/webhook"
Expand Down
28 changes: 25 additions & 3 deletions examples/server_notifications.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import asyncio
import os

from jellyfish import Notifier, RoomApi
from jellyfish.events import ServerMessageTrackAdded, ServerMessageTrackType

notifier = Notifier(server_address="localhost:5002", server_api_token="development")
HOST = "jellyfish" if os.getenv("DOCKER_TEST") == "TRUE" else "localhost"
SERVER_ADDRESS = f"{HOST}:5002"

notifier = Notifier(server_address=SERVER_ADDRESS, server_api_token="development")

notifier_task = None

LIMIT = os.getenv("CI_LIMIT", None)

if LIMIT is not None:
LIMIT = int(LIMIT)


counter = 0


@notifier.on_server_notification
Expand All @@ -20,19 +34,27 @@ def handle_notification(server_notification):
@notifier.on_metrics
def handle_metrics(metrics_report):
print(f"Received WebRTC metrics: {metrics_report}")
global counter
if LIMIT and counter > LIMIT:
notifier_task.cancel()
counter += 1


async def test_notifier():
global notifier_task
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 = RoomApi(server_address=SERVER_ADDRESS)
room_api.create_room()

await notifier_task
try:
await notifier_task
except asyncio.CancelledError:
print("Notifier task canceled, exiting")


asyncio.run(test_notifier())
23 changes: 16 additions & 7 deletions jellyfish/api/_room_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
RoomApi used to manage rooms
"""

from typing import Literal, Union
from typing import List, Literal, Tuple, Union

from jellyfish._openapi_client.api.hls import subscribe_hls_to as hls_subscribe_hls_to
from jellyfish._openapi_client.api.room import add_component as room_add_component
Expand Down Expand Up @@ -62,7 +62,7 @@ def create_room(
max_peers: int = None,
video_codec: Literal["h264", "vp8"] = None,
webhook_url: str = None,
) -> (str, Room):
) -> Tuple[str, Room]:
"""
Creates a new room
Expand Down Expand Up @@ -104,14 +104,16 @@ def get_room(self, room_id: str) -> Room:

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

def add_peer(self, room_id: str, options: PeerOptionsWebRTC) -> (str, Peer):
def add_peer(self, room_id: str, options: PeerOptionsWebRTC) -> Tuple[str, Peer]:
"""
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`
to authenticate to Jellyfish and the new `Peer`.
The possible options to pass for peer are `PeerOptionsWebRTC`.
"""

peer_type = "webrtc"
Expand All @@ -135,7 +137,14 @@ def add_component(
ComponentOptionsSIP,
],
) -> Union[ComponentFile, ComponentHLS, ComponentRTSP, ComponentSIP]:
"""Creates component in the room"""
"""
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`
* RTSP Component which options are `ComponentOptionsRTSP`
* SIP Component which options are `ComponentOptionsSIP`
"""

if isinstance(options, ComponentOptionsFile):
component_type = "file"
Expand All @@ -147,7 +156,7 @@ def add_component(
component_type = "sip"
else:
raise ValueError(
"options must be ComponentFile, ComponentOptionsHLS,"
"options must be ComponentOptionsFile, ComponentOptionsHLS,"
"ComponentOptionsRTSP or ComponentOptionsSIP"
)

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

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

def hls_subscribe(self, room_id: str, origins: [str]):
def hls_subscribe(self, room_id: str, origins: List[str]):
"""
In order to subscribe to HLS peers/components,
the HLS component should be initialized with the subscribe_mode set to manual.
Expand Down
28 changes: 25 additions & 3 deletions poetry_scripts.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import os
import shutil
import subprocess
import sys
from pathlib import Path


def check_exit_code(command):
command_exit_code = os.system(command)
if command_exit_code != 0:
sys.exit(command_exit_code >> 8)
process = subprocess.Popen(
command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)

while True:
output = process.stdout.readline()
if output == b"" and process.poll() is not None:
break
if output:
print(str(output.strip(), "utf-8"))
exit_code = process.poll()
if exit_code != 0:
sys.exit(exit_code)


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


def run_examples():
print("Start examples")

examples = os.listdir("./examples")

for example in examples:
check_exit_code(f"python ./examples/{example}")
print(f"After example from file: {example}")
print("All examples run without errors")


def run_local_test():
check_exit_code('poetry run pytest -m "not file_component_sources"')

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ lint = "poetry_scripts:run_linter"
fix_lint = "poetry_scripts:run_linter_fix"
generate_docs = "poetry_scripts:generate_docs"
update_client = "poetry_scripts:update_client"
examples = "poetry_scripts:run_examples"

[tool.ruff]
select = ["F", "I"]
Expand Down

0 comments on commit 9d81efe

Please sign in to comment.