Skip to content

Commit 135edaa

Browse files
authored
improve & expose EventEmitter (#284)
1 parent 5a9a6a4 commit 135edaa

File tree

7 files changed

+200
-52
lines changed

7 files changed

+200
-52
lines changed

livekit-rtc/livekit/rtc/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
Track,
5858
VideoTrack,
5959
)
60+
from .event_emitter import EventEmitter
6061
from .track_publication import (
6162
LocalTrackPublication,
6263
RemoteTrackPublication,
@@ -131,6 +132,7 @@
131132
"ChatMessage",
132133
"AudioResampler",
133134
"AudioResamplerQuality",
135+
"EventEmitter",
134136
"combine_audio_frames",
135137
"__version__",
136138
]

livekit-rtc/livekit/rtc/_event_emitter.py

Lines changed: 0 additions & 48 deletions
This file was deleted.

livekit-rtc/livekit/rtc/_ffi_client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727

2828
from ._proto import ffi_pb2 as proto_ffi
2929
from ._utils import Queue, classproperty
30-
31-
logger = logging.getLogger("livekit")
30+
from .log import logger
3231

3332
_resource_files = ExitStack()
3433
atexit.register(_resource_files.close)

livekit-rtc/livekit/rtc/chat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from typing import Any, Dict, Literal, Optional
2020

2121
from .room import Room, Participant, DataPacket
22-
from ._event_emitter import EventEmitter
22+
from .event_emitter import EventEmitter
2323
from ._utils import generate_random_base62
2424

2525
_CHAT_TOPIC = "lk-chat-topic"
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import inspect
2+
from typing import Callable, Dict, Set, Optional, Generic, TypeVar
3+
4+
from .log import logger
5+
6+
T = TypeVar("T")
7+
8+
9+
class EventEmitter(Generic[T]):
10+
def __init__(self) -> None:
11+
"""
12+
Initialize a new instance of EventEmitter.
13+
"""
14+
self._events: Dict[T, Set[Callable]] = dict()
15+
16+
def emit(self, event: T, *args) -> None:
17+
"""
18+
Trigger all callbacks associated with the given event.
19+
20+
Args:
21+
event (T): The event to emit.
22+
*args: Positional arguments to pass to the callbacks.
23+
24+
Example:
25+
Basic usage of emit:
26+
27+
```python
28+
emitter = EventEmitter[str]()
29+
30+
def greet(name):
31+
print(f"Hello, {name}!")
32+
33+
emitter.on('greet', greet)
34+
emitter.emit('greet', 'Alice') # Output: Hello, Alice!
35+
```
36+
"""
37+
if event in self._events:
38+
callables = self._events[event].copy()
39+
for callback in callables:
40+
try:
41+
sig = inspect.signature(callback)
42+
params = sig.parameters.values()
43+
44+
has_varargs = any(p.kind == p.VAR_POSITIONAL for p in params)
45+
if has_varargs:
46+
callback(*args)
47+
else:
48+
positional_params = [
49+
p
50+
for p in params
51+
if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD)
52+
]
53+
num_params = len(positional_params)
54+
num_args = min(len(args), num_params)
55+
callback_args = args[:num_args]
56+
57+
callback(*callback_args)
58+
except Exception:
59+
logger.exception(f"failed to emit event {event}")
60+
61+
def once(self, event: T, callback: Optional[Callable] = None) -> Callable:
62+
"""
63+
Register a callback to be called only once when the event is emitted.
64+
65+
If a callback is provided, it registers the callback directly.
66+
If no callback is provided, it returns a decorator for use with function definitions.
67+
68+
Args:
69+
event (T): The event to listen for.
70+
callback (Callable, optional): The callback to register. Defaults to None.
71+
72+
Returns:
73+
Callable: The registered callback or a decorator if callback is None.
74+
75+
Example:
76+
Using once with a direct callback:
77+
78+
```python
79+
emitter = EventEmitter[str]()
80+
81+
def greet_once(name):
82+
print(f"Hello once, {name}!")
83+
84+
emitter.once('greet', greet_once)
85+
emitter.emit('greet', 'Bob') # Output: Hello once, Bob!
86+
emitter.emit('greet', 'Bob') # No output, callback was removed after first call
87+
```
88+
89+
Using once as a decorator:
90+
91+
```python
92+
emitter = EventEmitter[str]()
93+
94+
@emitter.once('greet')
95+
def greet_once(name):
96+
print(f"Hello once, {name}!")
97+
98+
emitter.emit('greet', 'Bob') # Output: Hello once, Bob!
99+
emitter.emit('greet', 'Bob') # No output
100+
```
101+
"""
102+
if callback is not None:
103+
104+
def once_callback(*args, **kwargs):
105+
self.off(event, once_callback)
106+
callback(*args, **kwargs)
107+
108+
return self.on(event, once_callback)
109+
else:
110+
111+
def decorator(callback: Callable) -> Callable:
112+
self.once(event, callback)
113+
return callback
114+
115+
return decorator
116+
117+
def on(self, event: T, callback: Optional[Callable] = None) -> Callable:
118+
"""
119+
Register a callback to be called whenever the event is emitted.
120+
121+
If a callback is provided, it registers the callback directly.
122+
If no callback is provided, it returns a decorator for use with function definitions.
123+
124+
Args:
125+
event (T): The event to listen for.
126+
callback (Callable, optional): The callback to register. Defaults to None.
127+
128+
Returns:
129+
Callable: The registered callback or a decorator if callback is None.
130+
131+
Example:
132+
Using on with a direct callback:
133+
134+
```python
135+
emitter = EventEmitter[str]()
136+
137+
def greet(name):
138+
print(f"Hello, {name}!")
139+
140+
emitter.on('greet', greet)
141+
emitter.emit('greet', 'Charlie') # Output: Hello, Charlie!
142+
```
143+
144+
Using on as a decorator:
145+
146+
```python
147+
emitter = EventEmitter[str]()
148+
149+
@emitter.on('greet')
150+
def greet(name):
151+
print(f"Hello, {name}!")
152+
153+
emitter.emit('greet', 'Charlie') # Output: Hello, Charlie!
154+
```
155+
"""
156+
if callback is not None:
157+
if event not in self._events:
158+
self._events[event] = set()
159+
self._events[event].add(callback)
160+
return callback
161+
else:
162+
163+
def decorator(callback: Callable) -> Callable:
164+
self.on(event, callback)
165+
return callback
166+
167+
return decorator
168+
169+
def off(self, event: T, callback: Callable) -> None:
170+
"""
171+
Unregister a callback from an event.
172+
173+
Args:
174+
event (T): The event to stop listening to.
175+
callback (Callable): The callback to remove.
176+
177+
Example:
178+
Removing a callback:
179+
180+
```python
181+
emitter = EventEmitter[str]()
182+
183+
def greet(name):
184+
print(f"Hello, {name}!")
185+
186+
emitter.on('greet', greet)
187+
emitter.off('greet', greet)
188+
emitter.emit('greet', 'Dave') # No output, callback was removed
189+
```
190+
"""
191+
if event in self._events:
192+
self._events[event].remove(callback)

livekit-rtc/livekit/rtc/log.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import logging
2+
3+
logger = logging.getLogger("livekit")

livekit-rtc/livekit/rtc/room.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from dataclasses import dataclass, field
2020
from typing import Callable, Dict, Literal, Optional, cast
2121

22-
from ._event_emitter import EventEmitter
22+
from .event_emitter import EventEmitter
2323
from ._ffi_client import FfiClient, FfiHandle
2424
from ._proto import ffi_pb2 as proto_ffi
2525
from ._proto import participant_pb2 as proto_participant

0 commit comments

Comments
 (0)