Skip to content

Commit

Permalink
Improve API ergonomics, updated examples and readme.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidzhao committed Nov 11, 2023
1 parent d255eb5 commit f117e18
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 82 deletions.
82 changes: 58 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,74 @@

[![pypi-v](https://img.shields.io/pypi/v/livekit.svg)](https://pypi.org/project/livekit/)

# 📹🎙️🐍 Python Client SDK for LiveKit
# 📹🎙️🐍 Python SDK for LiveKit

The Livekit Python Client provides a convenient interface for integrating Livekit's real-time video and audio capabilities into your Python applications. With this library, developers can easily leverage Livekit's WebRTC functionalities, allowing them to focus on building their AI models or other application logic without worrying about the complexities of WebRTC.
<!--BEGIN_DESCRIPTION-->

Official LiveKit documentation: https://docs.livekit.io/
The LiveKit Python SDK provides a convenient interface for integrating LiveKit's real-time video and audio capabilities into your Python applications. With it, developers can easily leverage LiveKit's WebRTC functionalities, allowing them to focus on building their AI models or other application logic without worrying about the complexities of WebRTC.

## Installation
<!--END_DESCRIPTION-->

This repo contains two packages

- [livekit](https://pypi.org/project/livekit/): Real-time SDK for connecting to LiveKit as a participant
- [livekit-api](https://pypi.org/project/livekit-api/): Access token generation and server APIs

## Using Server API

RTC Client:
```shell
$ pip install livekit
$ pip install livekit-api
```

### Generating an access token

```python
from livekit import api

token = api.AccessToken(info.api_key, info.api_secret) \
.with_identity("python-bot") \
.with_name("Python Bot") \
.with_grants(api.VideoGrants(
room_join=True,
room="my-room",
)).to_jwt()
```

### Creating a room

RoomService uses asyncio and aiohttp to make API calls. It needs to be used with an event loop.

```python
from livekit import api
import asyncio

async def main():
# loads connection info from environment variables
# LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET
info = api.ConnectionInfo()
room_service = api.RoomService(
info.http_url(),
info.api_key,
info.api_secret,
)
room_info = await room_service.create_room(
api.room.CreateRoomRequest(name="my-room"),
)
print(room_info)
results = await room_service.list_rooms(api.room.ListRoomsRequest())
print(results)
await room_service.aclose()

asyncio.get_event_loop().run_until_complete(main())
```

API / Server SDK:
## Using Real-time SDK

```shell
$ pip install livekit-api
$ pip install livekit
```

## Connecting to a room
### Connecting to a room

```python
from livekit import rtc
Expand Down Expand Up @@ -64,21 +113,6 @@ async def main():
print("track publication: %s", publication.sid)
```

## Create a new access token

```python
from livekit import api

token = api.AccessToken("API_KEY", "SECRET_KEY")
token = AccessToken()
jwt = (
token.with_identity("user1")
.with_name("user1")
.with_grants(VideoGrants(room_join=True, room="room1"))
.to_jwt()
)
```

## Examples

- [Facelandmark](https://github.com/livekit/client-sdk-python/tree/main/examples/face_landmark): Use mediapipe to detect face landmarks (eyes, nose ...)
Expand Down
65 changes: 38 additions & 27 deletions examples/publish_hue.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,45 @@
from signal import SIGINT, SIGTERM

import numpy as np
from livekit import rtc

URL = "ws://localhost:7880"
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MDY2MTMyODgsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NzI2MTMyODgsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb20iOiJ0ZXN0Iiwicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZSwicm9vbUxpc3QiOnRydWV9fQ.uSNIangMRu8jZD5mnRYoCHjcsQWCrJXgHCs0aNIgBFY" # noqa
from livekit import api, rtc

WIDTH, HEIGHT = 1280, 720


# ensure LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET are set


async def main(room: rtc.Room):
info = api.ConnectionInfo()
token = api.AccessToken(info.api_key, info.api_secret) \
.with_identity("python-publisher") \
.with_name("Python Publisher") \
.with_grants(api.VideoGrants(
room_join=True,
room="my-room",
)).to_jwt()
logging.info("connecting to %s", info.websocket_url())
try:
await room.connect(info.websocket_url(), token)
logging.info("connected to room %s", room.name)
except rtc.ConnectError as e:
logging.error("failed to connect to the room: %s", e)
return

# publish a track
source = rtc.VideoSource(WIDTH, HEIGHT)
track = rtc.LocalVideoTrack.create_video_track("hue", source)
options = rtc.TrackPublishOptions()
options.source = rtc.TrackSource.SOURCE_CAMERA
publication = await room.local_participant.publish_track(track, options)
logging.info("published track %s", publication.sid)

asyncio.ensure_future(draw_color_cycle(source))


async def draw_color_cycle(source: rtc.VideoSource):
argb_frame = rtc.ArgbFrame.create(rtc.VideoFormatType.FORMAT_ARGB, WIDTH, HEIGHT)
argb_frame = rtc.ArgbFrame.create(
rtc.VideoFormatType.FORMAT_ARGB, WIDTH, HEIGHT)
arr = np.frombuffer(argb_frame.data, dtype=np.uint8)

framerate = 1 / 30
Expand Down Expand Up @@ -42,30 +71,11 @@ async def draw_color_cycle(source: rtc.VideoSource):
await asyncio.sleep(1 / 30 - code_duration)


async def main(room: rtc.Room):
logging.info("connecting to %s", URL)
try:
await room.connect(URL, TOKEN)
logging.info("connected to room %s", room.name)
except rtc.ConnectError as e:
logging.error("failed to connect to the room: %s", e)
return

# publish a track
source = rtc.VideoSource(WIDTH, HEIGHT)
track = rtc.LocalVideoTrack.create_video_track("hue", source)
options = rtc.TrackPublishOptions()
options.source = rtc.TrackSource.SOURCE_CAMERA
publication = await room.local_participant.publish_track(track, options)
logging.info("published track %s", publication.sid)

asyncio.ensure_future(draw_color_cycle(source))


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

loop = asyncio.get_event_loop()
Expand All @@ -77,7 +87,8 @@ 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
58 changes: 35 additions & 23 deletions examples/publish_wave.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,33 @@
from signal import SIGINT, SIGTERM

import numpy as np
from livekit import rtc

URL = "ws://localhost:7880"
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE5MDY2MTMyODgsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NzI2MTMyODgsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb20iOiJ0ZXN0Iiwicm9vbUFkbWluIjp0cnVlLCJyb29tQ3JlYXRlIjp0cnVlLCJyb29tSm9pbiI6dHJ1ZSwicm9vbUxpc3QiOnRydWV9fQ.uSNIangMRu8jZD5mnRYoCHjcsQWCrJXgHCs0aNIgBFY" # noqa
from livekit import rtc, api

SAMPLE_RATE = 48000
NUM_CHANNELS = 1


async def publish_frames(source: rtc.AudioSource, frequency: int):
amplitude = 32767 # for 16-bit audio
samples_per_channel = 480 # 10ms at 48kHz
time = np.arange(samples_per_channel) / SAMPLE_RATE
total_samples = 0
audio_frame = rtc.AudioFrame.create(SAMPLE_RATE, NUM_CHANNELS, samples_per_channel)
audio_data = np.frombuffer(audio_frame.data, dtype=np.int16)
while True:
time = (total_samples + np.arange(samples_per_channel)) / SAMPLE_RATE
sine_wave = (amplitude * np.sin(2 * np.pi * frequency * time)).astype(np.int16)
np.copyto(audio_data, sine_wave)
await source.capture_frame(audio_frame)
total_samples += samples_per_channel
# ensure LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET are set


async def main(room: rtc.Room) -> None:
@room.on("participant_disconnected")
def on_participant_disconnect(participant: rtc.Participant, *_):
logging.info("participant disconnected: %s", participant.identity)

logging.info("connecting to %s", URL)
info = api.ConnectionInfo()
token = api.AccessToken(info.api_key, info.api_secret) \
.with_identity("python-publisher") \
.with_name("Python Publisher") \
.with_grants(api.VideoGrants(
room_join=True,
room="my-room",
)).to_jwt()

logging.info("connecting to %s", info.websocket_url())
try:
await room.connect(
URL,
TOKEN,
info.websocket_url(),
token,
options=rtc.RoomOptions(
auto_subscribe=True,
),
Expand All @@ -57,10 +50,28 @@ def on_participant_disconnect(participant: rtc.Participant, *_):
asyncio.ensure_future(publish_frames(source, 440))


async def publish_frames(source: rtc.AudioSource, frequency: int):
amplitude = 32767 # for 16-bit audio
samples_per_channel = 480 # 10ms at 48kHz
time = np.arange(samples_per_channel) / SAMPLE_RATE
total_samples = 0
audio_frame = rtc.AudioFrame.create(
SAMPLE_RATE, NUM_CHANNELS, samples_per_channel)
audio_data = np.frombuffer(audio_frame.data, dtype=np.int16)
while True:
time = (total_samples + np.arange(samples_per_channel)) / SAMPLE_RATE
sine_wave = (amplitude * np.sin(2 * np.pi *
frequency * time)).astype(np.int16)
np.copyto(audio_data, sine_wave)
await source.capture_frame(audio_frame)
total_samples += samples_per_channel


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

loop = asyncio.get_event_loop()
Expand All @@ -72,7 +83,8 @@ 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
3 changes: 3 additions & 0 deletions livekit-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# LiveKit Server APIs

Access LiveKit server APIs and generate access tokens.
10 changes: 6 additions & 4 deletions livekit-api/livekit/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
"""

# flake8: noqa
from livekit.protocol.egress import *
from livekit.protocol.ingress import *
from livekit.protocol.models import *
from livekit.protocol.room import *
# re-export packages from protocol
from livekit.protocol import egress
from livekit.protocol import ingress
from livekit.protocol import models
from livekit.protocol import room

from .connection_info import ConnectionInfo
from .access_token import VideoGrants, AccessToken
from .room_service import RoomService
from .version import __version__
22 changes: 22 additions & 0 deletions livekit-api/livekit/api/connection_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import os


class ConnectionInfo:
def __init__(self):
self.url = os.getenv("LIVEKIT_URL", "ws://localhost:7880")
self.api_key = os.getenv("LIVEKIT_API_KEY")
self.api_secret = os.getenv("LIVEKIT_API_SECRET")

if not self.api_key or not self.api_secret:
raise ValueError(
"LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set")

def websocket_url(self) -> str:
if self.url.startswith("http"):
return self.url.replace("http", "ws", 1)
return self.url

def http_url(self) -> str:
if self.url.startswith("ws"):
return self.url.replace("ws", "http", 1)
return self.url
4 changes: 2 additions & 2 deletions livekit-api/livekit/api/room_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@


class RoomService(Service):
def __init__(self, host: str, api_key: str, api_secret: str):
super().__init__(host, api_key, api_secret)
def __init__(self, url: str, api_key: str, api_secret: str):
super().__init__(url, api_key, api_secret)

async def create_room(
self, create: proto_room.CreateRoomRequest
Expand Down
2 changes: 1 addition & 1 deletion livekit-api/livekit/api/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.3"
__version__ = "0.1.4"
4 changes: 3 additions & 1 deletion livekit-rtc/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# livekit-rtc
# LiveKit Real-time Python SDK

The LiveKit Python SDK provides a convenient interface for integrating LiveKit's real-time video and audio capabilities into your Python applications. With it, developers can easily leverage LiveKit's WebRTC functionalities, allowing them to focus on building their AI models or other application logic without worrying about the complexities of WebRTC.

0 comments on commit f117e18

Please sign in to comment.