Skip to content

Commit 0994e75

Browse files
committed
fixed bugs & more examples
1 parent 15092ae commit 0994e75

File tree

12 files changed

+303
-38
lines changed

12 files changed

+303
-38
lines changed

client-sdk-rust

examples/basic_room/room.py

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,73 @@
11
import livekit
2+
import logging
23
import asyncio
4+
from signal import SIGINT, SIGTERM
35

46
URL = 'ws://localhost:7880'
57
TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MDY2MTMyODgsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NzI2MTMyODgsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb20iOiJ0ZXN0Iiwicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZSwicm9vbUxpc3QiOnRydWV9fQ.uSNIangMRu8jZD5mnRYoCHjcsQWCrJXgHCs0aNIgBFY'
68

79

810
async def main():
911
room = livekit.Room()
10-
await room.connect(URL, TOKEN)
11-
print("connected to room: " + room.name)
12+
13+
logging.info("connecting to %s", URL)
14+
try:
15+
await room.connect(URL, TOKEN)
16+
logging.info("connected to room %s", room.name)
17+
except livekit.ConnectError as e:
18+
logging.error("failed to connect to the room: %s", e)
19+
return False
20+
21+
@room.on("participant_connected")
22+
def on_participant_connected(participant: livekit.RemoteParticipant):
23+
logging.info(
24+
"participant connected: %s %s", participant.sid, participant.identity)
25+
26+
@room.on("participant_disconnected")
27+
def on_participant_disconnected(participant: livekit.RemoteParticipant):
28+
logging.info("participant disconnected: %s %s",
29+
participant.sid, participant.identity)
30+
31+
@room.on("track_published")
32+
def on_track_published(publication: livekit.LocalTrackPublication, participant: livekit.RemoteParticipant):
33+
logging.info("track published: %s from participant %s (%s)",
34+
publication.sid, participant.sid, participant.identity)
35+
36+
@room.on("track_unpublished")
37+
def on_track_unpublished(publication: livekit.LocalTrackPublication, participant: livekit.RemoteParticipant):
38+
logging.info("track unpublished: %s", publication.sid)
1239

1340
@room.on("track_subscribed")
1441
def on_track_subscribed(track: livekit.Track, publication: livekit.RemoteTrackPublication, participant: livekit.RemoteParticipant):
42+
logging.info("track subscribed: %s", publication.sid)
1543
if track.kind == livekit.TrackKind.KIND_VIDEO:
1644
video_stream = livekit.VideoStream(track)
1745

1846
@video_stream.on("frame_received")
1947
def on_video_frame(frame: livekit.VideoFrame):
20-
print("received video frame")
48+
# e.g: Do something with the frames here :)
2149
pass
2250

23-
await room.run()
51+
@room.on("track_unsubscribed")
52+
def on_track_unsubscribed(track: livekit.Track, publication: livekit.RemoteTrackPublication, participant: livekit.RemoteParticipant):
53+
logging.info("track unsubscribed: %s", publication.sid)
54+
55+
try:
56+
await room.run()
57+
except asyncio.CancelledError:
58+
logging.info("closing the room")
59+
await room.close()
2460

2561

2662
if __name__ == "__main__":
27-
asyncio.run(main())
63+
logging.basicConfig(level=logging.INFO, handlers=[
64+
logging.FileHandler("basic_room.log"), logging.StreamHandler()])
65+
66+
loop = asyncio.get_event_loop()
67+
main_task = asyncio.ensure_future(main())
68+
for signal in [SIGINT, SIGTERM]:
69+
loop.add_signal_handler(signal, main_task.cancel)
70+
try:
71+
loop.run_until_complete(main_task)
72+
finally:
73+
loop.close()

examples/face_landmark/room.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import matplotlib.pyplot as plt
2+
import livekit
3+
import asyncio
4+
import cv2
5+
import numpy as np
6+
import os
7+
from queue import Queue
8+
import mediapipe as mp
9+
from mediapipe.tasks import python
10+
from mediapipe.tasks.python import vision
11+
from mediapipe import solutions
12+
from mediapipe.framework.formats import landmark_pb2
13+
import numpy as np
14+
15+
URL = 'ws://localhost:7880'
16+
TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MDY2MTMyODgsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NzI2MTMyODgsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb20iOiJ0ZXN0Iiwicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZSwicm9vbUxpc3QiOnRydWV9fQ.uSNIangMRu8jZD5mnRYoCHjcsQWCrJXgHCs0aNIgBFY'
17+
18+
frame_queue = Queue()
19+
argb_frame = None
20+
21+
# You can downlo9ad a face landmark model file from https://developers.google.com/mediapipe/solutions/vision/face_landmarker#models
22+
model_file = 'face_landmarker.task'
23+
model_path = os.path.dirname(os.path.realpath(__file__)) + '/' + model_file
24+
25+
BaseOptions = mp.tasks.BaseOptions
26+
FaceLandmarker = mp.tasks.vision.FaceLandmarker
27+
FaceLandmarkerOptions = mp.tasks.vision.FaceLandmarkerOptions
28+
VisionRunningMode = mp.tasks.vision.RunningMode
29+
30+
options = FaceLandmarkerOptions(
31+
base_options=BaseOptions(model_asset_path=model_path),
32+
running_mode=VisionRunningMode.VIDEO)
33+
34+
# from https://github.com/googlesamples/mediapipe/blob/main/examples/face_landmarker/python/%5BMediaPipe_Python_Tasks%5D_Face_Landmarker.ipynb
35+
36+
37+
def draw_landmarks_on_image(rgb_image, detection_result):
38+
face_landmarks_list = detection_result.face_landmarks
39+
40+
# Loop through the detected faces to visualize.
41+
for idx in range(len(face_landmarks_list)):
42+
face_landmarks = face_landmarks_list[idx]
43+
44+
# Draw the face landmarks.
45+
face_landmarks_proto = landmark_pb2.NormalizedLandmarkList()
46+
face_landmarks_proto.landmark.extend([
47+
landmark_pb2.NormalizedLandmark(x=landmark.x, y=landmark.y, z=landmark.z) for landmark in face_landmarks
48+
])
49+
50+
solutions.drawing_utils.draw_landmarks(
51+
image=rgb_image,
52+
landmark_list=face_landmarks_proto,
53+
connections=mp.solutions.face_mesh.FACEMESH_TESSELATION,
54+
landmark_drawing_spec=None,
55+
connection_drawing_spec=mp.solutions.drawing_styles
56+
.get_default_face_mesh_tesselation_style())
57+
solutions.drawing_utils.draw_landmarks(
58+
image=rgb_image,
59+
landmark_list=face_landmarks_proto,
60+
connections=mp.solutions.face_mesh.FACEMESH_CONTOURS,
61+
landmark_drawing_spec=None,
62+
connection_drawing_spec=mp.solutions.drawing_styles
63+
.get_default_face_mesh_contours_style())
64+
solutions.drawing_utils.draw_landmarks(
65+
image=rgb_image,
66+
landmark_list=face_landmarks_proto,
67+
connections=mp.solutions.face_mesh.FACEMESH_IRISES,
68+
landmark_drawing_spec=None,
69+
connection_drawing_spec=mp.solutions.drawing_styles
70+
.get_default_face_mesh_iris_connections_style())
71+
72+
73+
async def room():
74+
room = livekit.Room()
75+
await room.connect(URL, TOKEN)
76+
print("connected to room: " + room.name)
77+
78+
@room.on("track_subscribed")
79+
def on_track_subscribed(track: livekit.Track, publication: livekit.RemoteTrackPublication, participant: livekit.RemoteParticipant):
80+
if track.kind == livekit.TrackKind.KIND_VIDEO:
81+
video_stream = livekit.VideoStream(track)
82+
83+
@video_stream.on("frame_received")
84+
def on_video_frame(frame: livekit.VideoFrame):
85+
frame_queue.put(frame)
86+
87+
await room.run()
88+
89+
90+
def display_frames():
91+
cv2.namedWindow('livekit_video', cv2.WINDOW_AUTOSIZE)
92+
cv2.startWindowThread()
93+
94+
global argb_frame
95+
96+
with FaceLandmarker.create_from_options(options) as landmarker:
97+
while True:
98+
frame = frame_queue.get()
99+
buffer = frame.buffer
100+
101+
if argb_frame is None or argb_frame.width != buffer.width or argb_frame.height != buffer.height:
102+
argb_frame = livekit.ArgbFrame(
103+
livekit.VideoFormatType.FORMAT_ABGR, buffer.width, buffer.height)
104+
105+
buffer.to_argb(argb_frame)
106+
107+
arr = np.ctypeslib.as_array(argb_frame.data)
108+
arr = arr.reshape((argb_frame.height, argb_frame.width, 4))
109+
arr = cv2.cvtColor(arr, cv2.COLOR_RGBA2RGB)
110+
111+
mp_image = mp.Image(
112+
image_format=mp.ImageFormat.SRGB, data=arr)
113+
114+
detection_result = landmarker.detect_for_video(
115+
mp_image, frame.timestamp)
116+
117+
draw_landmarks_on_image(arr, detection_result)
118+
119+
arr = cv2.cvtColor(arr, cv2.COLOR_RGB2BGR)
120+
121+
cv2.imshow('livekit_video', arr)
122+
if cv2.waitKey(1) & 0xFF == ord('q'):
123+
break
124+
125+
cv2.destroyAllWindows()
126+
127+
128+
async def main():
129+
loop = asyncio.get_event_loop()
130+
future = loop.run_in_executor(None, asyncio.run, room())
131+
132+
display_frames()
133+
await future
134+
135+
if __name__ == "__main__":
136+
asyncio.run(main())

examples/publish_hue/room.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import livekit
2+
import logging
3+
import numpy as np
4+
import colorsys
5+
import asyncio
6+
from signal import SIGINT, SIGTERM
7+
8+
URL = 'ws://localhost:7880'
9+
TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MDY2MTMyODgsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NzI2MTMyODgsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb20iOiJ0ZXN0Iiwicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZSwicm9vbUxpc3QiOnRydWV9fQ.uSNIangMRu8jZD5mnRYoCHjcsQWCrJXgHCs0aNIgBFY'
10+
11+
12+
async def publish_frames(source: livekit.VideoSource):
13+
argb_frame = livekit.ArgbFrame(
14+
livekit.VideoFormatType.FORMAT_ARGB, 1280, 720)
15+
16+
arr = np.ctypeslib.as_array(argb_frame.data)
17+
18+
framerate = 1 / 30
19+
hue = 0
20+
21+
while True:
22+
frame = livekit.VideoFrame(
23+
0, livekit.VideoRotation.VIDEO_ROTATION_0, argb_frame.to_i420())
24+
25+
rgb = colorsys.hsv_to_rgb(hue, 1.0, 1.0)
26+
rgb = [int(x * 255) for x in rgb]
27+
argb_color = rgb + [255]
28+
29+
for i in range(0, len(arr), 4):
30+
arr[i:i+4] = argb_color
31+
32+
source.capture_frame(frame)
33+
34+
hue += framerate/2 # 3s for a full cycle
35+
if hue >= 1.0:
36+
hue -= 1.0
37+
38+
await asyncio.sleep(framerate)
39+
40+
41+
async def main():
42+
room = livekit.Room()
43+
44+
logging.info("connecting to %s", URL)
45+
try:
46+
await room.connect(URL, TOKEN)
47+
logging.info("connected to room %s", room.name)
48+
except livekit.ConnectError as e:
49+
logging.error("failed to connect to the room: %s", e)
50+
return False
51+
52+
# publish a track
53+
source = livekit.VideoSource()
54+
source_task = asyncio.create_task(publish_frames(source))
55+
56+
track = livekit.LocalVideoTrack.create_video_track("hue", source)
57+
options = livekit.TrackPublishOptions()
58+
options.source = livekit.TrackSource.SOURCE_CAMERA
59+
publication = await room.local_participant.publish_track(track, options)
60+
logging.info("published track %s", publication.sid)
61+
62+
try:
63+
await room.run()
64+
except asyncio.CancelledError:
65+
logging.info("closing the room")
66+
await room.close()
67+
68+
69+
if __name__ == "__main__":
70+
logging.basicConfig(level=logging.INFO, handlers=[
71+
logging.FileHandler("publish_hue.log"), logging.StreamHandler()])
72+
73+
loop = asyncio.get_event_loop()
74+
main_task = asyncio.ensure_future(main())
75+
for signal in [SIGINT, SIGTERM]:
76+
loop.add_signal_handler(signal, main_task.cancel)
77+
try:
78+
loop.run_until_complete(main_task)
79+
finally:
80+
loop.close()

livekit/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from ._proto.track_pb2 import (TrackKind, TrackSource, StreamState)
99
from ._proto.room_pb2 import (TrackPublishOptions)
1010

11-
from .room import Room
11+
from .room import (Room, ConnectError)
1212
from .participant import (Participant, LocalParticipant, RemoteParticipant)
1313
from .track import (Track, LocalAudioTrack, LocalVideoTrack,
1414
RemoteAudioTrack, RemoteVideoTrack)

livekit/_ffi_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,5 @@ def __init__(self, handle: int):
108108

109109
def __del__(self):
110110
if self.handle != INVALID_HANDLE:
111-
ffi_lib.livekit_ffi_drop_handle(
112-
c_size_t(self.handle)) # TODO Assert
111+
assert ffi_lib.livekit_ffi_drop_handle(
112+
c_size_t(self.handle))

livekit/participant.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ def __init__(self, message: str):
1919

2020

2121
class Participant():
22-
def __init__(self, info: proto_participant.ParticipantInfo, room: weakref.ref['Room']):
22+
def __init__(self, info: proto_participant.ParticipantInfo):
2323
self._info = info
24-
self._room = room
2524
self.tracks: dict[str, TrackPublication] = {}
2625

2726
@property
@@ -43,7 +42,8 @@ def metadata(self) -> str:
4342

4443
class LocalParticipant(Participant):
4544
def __init__(self, info: proto_participant.ParticipantInfo, room: weakref.ref['Room']):
46-
super().__init__(info, room)
45+
super().__init__(info)
46+
self._room = room
4747

4848
async def publish_track(self, track: Track, options: TrackPublishOptions) -> TrackPublication:
4949
if not isinstance(track, LocalAudioTrack) and not isinstance(track, LocalVideoTrack):
@@ -56,15 +56,15 @@ async def publish_track(self, track: Track, options: TrackPublishOptions) -> Tra
5656
req = proto_ffi.FfiRequest()
5757
req.publish_track.track_handle.id = track._ffi_handle.handle
5858
req.publish_track.room_handle.id = room._ffi_handle.handle
59-
req.publish_track.options = options
59+
req.publish_track.options.CopyFrom(options)
6060

6161
ffi_client = FfiClient()
6262
resp = ffi_client.request(req)
6363
future = asyncio.Future()
6464

6565
@ffi_client.on('publish_track')
6666
def on_publish_callback(cb: proto_room.PublishTrackCallback):
67-
if cb.async_id == resp.async_id:
67+
if cb.async_id == resp.publish_track.async_id:
6868
future.set_result(cb)
6969
ffi_client.remove_listener(
7070
'publish_track', on_publish_callback)
@@ -82,5 +82,5 @@ def on_publish_callback(cb: proto_room.PublishTrackCallback):
8282

8383

8484
class RemoteParticipant(Participant):
85-
def __init__(self, info: proto_participant.ParticipantInfo, room: weakref.ref['Room']):
86-
super().__init__(info, room)
85+
def __init__(self, info: proto_participant.ParticipantInfo):
86+
super().__init__(info)

0 commit comments

Comments
 (0)