Skip to content

Commit

Permalink
pythonic API (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
theomonnom authored Oct 27, 2023
1 parent 907be69 commit 0949eea
Show file tree
Hide file tree
Showing 34 changed files with 1,655 additions and 792 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/ruff.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Ruff - Checks
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.9"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff
- name: Ruff livekit-api
run: ruff check --output-format=github .

- name: Check format
run: ruff format --check .

136 changes: 75 additions & 61 deletions examples/basic_room.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,61 @@

from livekit import rtc

URL = 'ws://localhost:7880'
TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MDY2MTMyODgsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NzI2MTMyODgsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb20iOiJ0ZXN0Iiwicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZSwicm9vbUxpc3QiOnRydWV9fQ.uSNIangMRu8jZD5mnRYoCHjcsQWCrJXgHCs0aNIgBFY' # noqa
URL = "ws://localhost:7880"
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MDY2MTMyODgsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NzI2MTMyODgsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb20iOiJ0ZXN0Iiwicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZSwicm9vbUxpc3QiOnRydWV9fQ.uSNIangMRu8jZD5mnRYoCHjcsQWCrJXgHCs0aNIgBFY" # noqa


async def main(room: rtc.Room) -> None:

@room.listens_to("participant_connected")
@room.on("participant_connected")
def on_participant_connected(participant: rtc.RemoteParticipant) -> None:
logging.info(
"participant connected: %s %s", participant.sid, participant.identity)
"participant connected: %s %s", participant.sid, participant.identity
)

@room.listens_to("participant_disconnected")
@room.on("participant_disconnected")
def on_participant_disconnected(participant: rtc.RemoteParticipant):
logging.info("participant disconnected: %s %s",
participant.sid, participant.identity)

@room.listens_to("local_track_published")
def on_local_track_published(publication: rtc.LocalTrackPublication,
track: Union[rtc.LocalAudioTrack,
rtc.LocalVideoTrack]):
logging.info(
"participant disconnected: %s %s", participant.sid, participant.identity
)

@room.on("local_track_published")
def on_local_track_published(
publication: rtc.LocalTrackPublication,
track: Union[rtc.LocalAudioTrack, rtc.LocalVideoTrack],
):
logging.info("local track published: %s", publication.sid)

@room.listens_to("active_speakers_changed")
@room.on("active_speakers_changed")
def on_active_speakers_changed(speakers: list[rtc.Participant]):
logging.info("active speakers changed: %s", speakers)

@room.listens_to("local_track_unpublished")
@room.on("local_track_unpublished")
def on_local_track_unpublished(publication: rtc.LocalTrackPublication):
logging.info("local track unpublished: %s", publication.sid)

@room.listens_to("track_published")
def on_track_published(publication: rtc.RemoteTrackPublication,
participant: rtc.RemoteParticipant):
logging.info("track published: %s from participant %s (%s)",
publication.sid, participant.sid, participant.identity)

@room.listens_to("track_unpublished")
def on_track_unpublished(publication: rtc.RemoteTrackPublication,
participant: rtc.RemoteParticipant):
@room.on("track_published")
def on_track_published(
publication: rtc.RemoteTrackPublication, participant: rtc.RemoteParticipant
):
logging.info(
"track published: %s from participant %s (%s)",
publication.sid,
participant.sid,
participant.identity,
)

@room.on("track_unpublished")
def on_track_unpublished(
publication: rtc.RemoteTrackPublication, participant: rtc.RemoteParticipant
):
logging.info("track unpublished: %s", publication.sid)

@room.listens_to("track_subscribed")
def on_track_subscribed(track: rtc.Track,
publication: rtc.RemoteTrackPublication,
participant: rtc.RemoteParticipant):
@room.on("track_subscribed")
def on_track_subscribed(
track: rtc.Track,
publication: rtc.RemoteTrackPublication,
participant: rtc.RemoteParticipant,
):
logging.info("track subscribed: %s", publication.sid)
if track.kind == rtc.TrackKind.KIND_VIDEO:
_video_stream = rtc.VideoStream(track)
Expand All @@ -59,57 +69,61 @@ def on_track_subscribed(track: rtc.Track,
_audio_stream = rtc.AudioStream(track)
# audio_stream is an async iterator that yields AudioFrame

@room.listens_to("track_unsubscribed")
def on_track_unsubscribed(track: rtc.Track,
publication: rtc.RemoteTrackPublication,
participant: rtc.RemoteParticipant):
@room.on("track_unsubscribed")
def on_track_unsubscribed(
track: rtc.Track,
publication: rtc.RemoteTrackPublication,
participant: rtc.RemoteParticipant,
):
logging.info("track unsubscribed: %s", publication.sid)

@room.listens_to("track_muted")
def on_track_muted(publication: rtc.RemoteTrackPublication,
participant: rtc.RemoteParticipant):
@room.on("track_muted")
def on_track_muted(
publication: rtc.RemoteTrackPublication, participant: rtc.RemoteParticipant
):
logging.info("track muted: %s", publication.sid)

@room.listens_to("track_unmuted")
def on_track_unmuted(publication: rtc.RemoteTrackPublication,
participant: rtc.RemoteParticipant):
@room.on("track_unmuted")
def on_track_unmuted(
publication: rtc.RemoteTrackPublication, participant: rtc.RemoteParticipant
):
logging.info("track unmuted: %s", publication.sid)

@room.listens_to("data_received")
def on_data_received(data: bytes,
kind: rtc.DataPacketKind,
participant: rtc.Participant):
@room.on("data_received")
def on_data_received(
data: bytes, kind: rtc.DataPacketKind, participant: rtc.Participant
):
logging.info("received data from %s: %s", participant.identity, data)

@room.listens_to("connection_quality_changed")
def on_connection_quality_changed(participant: rtc.Participant,
quality: rtc.ConnectionQuality):
@room.on("connection_quality_changed")
def on_connection_quality_changed(
participant: rtc.Participant, quality: rtc.ConnectionQuality
):
logging.info("connection quality changed for %s", participant.identity)

@room.listens_to("track_subscription_failed")
def on_track_subscription_failed(participant: rtc.RemoteParticipant,
track_sid: str,
error: str):
logging.info("track subscription failed: %s %s",
participant.identity, error)
@room.on("track_subscription_failed")
def on_track_subscription_failed(
participant: rtc.RemoteParticipant, track_sid: str, error: str
):
logging.info("track subscription failed: %s %s", participant.identity, error)

@room.listens_to("connection_state_changed")
@room.on("connection_state_changed")
def on_connection_state_changed(state: rtc.ConnectionState):
logging.info("connection state changed: %s", state)

@room.listens_to("connected")
@room.on("connected")
def on_connected() -> None:
logging.info("connected")

@room.listens_to("disconnected")
@room.on("disconnected")
def on_disconnected() -> None:
logging.info("disconnected")

@room.listens_to("reconnecting")
@room.on("reconnecting")
def on_reconnecting() -> None:
logging.info("reconnecting")

@room.listens_to("reconnected")
@room.on("reconnected")
def on_reconnected() -> None:
logging.info("reconnected")

Expand All @@ -122,9 +136,10 @@ def on_reconnected() -> None:


if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, handlers=[
logging.FileHandler("basic_room.log"),
logging.StreamHandler()])
logging.basicConfig(
level=logging.INFO,
handlers=[logging.FileHandler("basic_room.log"), logging.StreamHandler()],
)

loop = asyncio.get_event_loop()
room = rtc.Room(loop=loop)
Expand All @@ -135,8 +150,7 @@ async def cleanup():

asyncio.ensure_future(main(room))
for signal in [SIGINT, SIGTERM]:
loop.add_signal_handler(
signal, lambda: asyncio.ensure_future(cleanup()))
loop.add_signal_handler(signal, lambda: asyncio.ensure_future(cleanup()))

try:
loop.run_forever()
Expand Down
103 changes: 71 additions & 32 deletions examples/e2ee.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import numpy as np
from livekit import rtc

URL = 'ws://localhost:7880'
TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MDY2MTMyODgsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NzI2MTMyODgsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb20iOiJ0ZXN0Iiwicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZSwicm9vbUxpc3QiOnRydWV9fQ.uSNIangMRu8jZD5mnRYoCHjcsQWCrJXgHCs0aNIgBFY' # noqa
URL = "ws://localhost:7880"
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MDY2MTMyODgsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NzI2MTMyODgsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb20iOiJ0ZXN0Iiwicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZSwicm9vbUxpc3QiOnRydWV9fQ.uSNIangMRu8jZD5mnRYoCHjcsQWCrJXgHCs0aNIgBFY" # noqa

# ("livekitrocks") this is our shared key, it must match the one used by your clients
SHARED_KEY = b"liveitrocks"
Expand All @@ -15,40 +15,78 @@
async def draw_cube(source: rtc.VideoSource):
W, H, MID_W, MID_H = 1280, 720, 640, 360
cube_size = 60
vertices = (np.array([[-1, -1, -1], [1, -1, -1], [1, 1, -1], [-1, 1, -1],
[-1, -1, 1], [1, -1, 1], [1, 1, 1], [-1, 1, 1]]) * cube_size)
edges = [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6],
[6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7]]

frame = rtc.ArgbFrame(livekit.VideoFormatType.FORMAT_ARGB, W, H)
vertices = (
np.array(
[
[-1, -1, -1],
[1, -1, -1],
[1, 1, -1],
[-1, 1, -1],
[-1, -1, 1],
[1, -1, 1],
[1, 1, 1],
[-1, 1, 1],
]
)
* cube_size
)
edges = [
[0, 1],
[1, 2],
[2, 3],
[3, 0],
[4, 5],
[5, 6],
[6, 7],
[7, 4],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
]

frame = rtc.ArgbFrame(rtc.VideoFormatType.FORMAT_ARGB, W, H)
arr = np.ctypeslib.as_array(frame.data)
angle = 0

while True:
start_time = asyncio.get_event_loop().time()
arr.fill(0)
rot = np.dot(np.array([[1, 0, 0],
[0, np.cos(angle), -np.sin(angle)],
[0, np.sin(angle), np.cos(angle)]]),
np.array([[np.cos(angle), 0, np.sin(angle)],
[0, 1, 0],
[-np.sin(angle), 0, np.cos(angle)]]))
proj_points = [[int(pt[0] / (pt[2] / 200 + 1)), int(pt[1] / (pt[2] / 200 + 1))]
for pt in np.dot(vertices, rot)]
rot = np.dot(
np.array(
[
[1, 0, 0],
[0, np.cos(angle), -np.sin(angle)],
[0, np.sin(angle), np.cos(angle)],
]
),
np.array(
[
[np.cos(angle), 0, np.sin(angle)],
[0, 1, 0],
[-np.sin(angle), 0, np.cos(angle)],
]
),
)
proj_points = [
[int(pt[0] / (pt[2] / 200 + 1)), int(pt[1] / (pt[2] / 200 + 1))]
for pt in np.dot(vertices, rot)
]

for e in edges:
x1, y1, x2, y2 = *proj_points[e[0]], *proj_points[e[1]]
for t in np.linspace(0, 1, 100):
x, y = int(MID_W + (1 - t) * x1 + t *
x2), int(MID_H + (1 - t) * y1 + t * y2)
x, y = (
int(MID_W + (1 - t) * x1 + t * x2),
int(MID_H + (1 - t) * y1 + t * y2),
)
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if 0 <= x + dx < W and 0 <= y + dy < H:
idx = (y + dy) * W * 4 + (x + dx) * 4
arr[idx:idx+4] = [255, 255, 255, 255]
arr[idx : idx + 4] = [255, 255, 255, 255]

f = rtc.VideoFrame(
0, rtc.VideoRotation.VIDEO_ROTATION_0, frame.to_i420())
f = rtc.VideoFrame(0, rtc.VideoRotation.VIDEO_ROTATION_0, frame.to_i420())
source.capture_frame(f)
angle += 0.02

Expand All @@ -58,19 +96,19 @@ async def draw_cube(source: rtc.VideoSource):

async def main(room: rtc.Room):
@room.listens_to("e2ee_state_changed")
def on_e2ee_state_changed(participant: rtc.Participant,
state: rtc.EncryptionState) -> None:
def on_e2ee_state_changed(
participant: rtc.Participant, state: rtc.EncryptionState
) -> None:
logging.info("e2ee state changed: %s %s", participant.identity, state)

logging.info("connecting to %s", URL)
try:
e2ee_options = rtc.E2EEOptions()
e2ee_options.key_provider_options.shared_key = SHARED_KEY

await room.connect(URL, TOKEN, options=rtc.RoomOptions(
auto_subscribe=True,
e2ee=e2ee_options
))
await room.connect(
URL, TOKEN, options=rtc.RoomOptions(auto_subscribe=True, e2ee=e2ee_options)
)

logging.info("connected to room %s", room.name)
except rtc.ConnectError as e:
Expand All @@ -87,10 +125,12 @@ def on_e2ee_state_changed(participant: rtc.Participant,

asyncio.ensure_future(draw_cube(source))


if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, handlers=[
logging.FileHandler("e2ee.log"),
logging.StreamHandler()])
logging.basicConfig(
level=logging.INFO,
handlers=[logging.FileHandler("e2ee.log"), logging.StreamHandler()],
)

loop = asyncio.get_event_loop()
room = rtc.Room(loop=loop)
Expand All @@ -101,8 +141,7 @@ async def cleanup():

asyncio.ensure_future(main(room))
for signal in [SIGINT, SIGTERM]:
loop.add_signal_handler(
signal, lambda: asyncio.ensure_future(cleanup()))
loop.add_signal_handler(signal, lambda: asyncio.ensure_future(cleanup()))

try:
loop.run_forever()
Expand Down
Loading

0 comments on commit 0949eea

Please sign in to comment.