Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: api improvements #77

Merged
merged 26 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading