jrtc 3.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jrtc-3.1.0/LICENSE +21 -0
- jrtc-3.1.0/MANIFEST.in +2 -0
- jrtc-3.1.0/PKG-INFO +535 -0
- jrtc-3.1.0/README.md +499 -0
- jrtc-3.1.0/pyproject.toml +99 -0
- jrtc-3.1.0/setup.cfg +4 -0
- jrtc-3.1.0/src/jrtc/__init__.py +40 -0
- jrtc-3.1.0/src/jrtc/auth.py +60 -0
- jrtc-3.1.0/src/jrtc/conf/__init__.py +4 -0
- jrtc-3.1.0/src/jrtc/conf/_config.py +173 -0
- jrtc-3.1.0/src/jrtc/conf/_janus.py +70 -0
- jrtc-3.1.0/src/jrtc/conf/settings/__init__.py +45 -0
- jrtc-3.1.0/src/jrtc/conf/settings/global_settings.py +81 -0
- jrtc-3.1.0/src/jrtc/core/__init__.py +29 -0
- jrtc-3.1.0/src/jrtc/core/exceptions.py +73 -0
- jrtc-3.1.0/src/jrtc/core/logging/__init__.py +19 -0
- jrtc-3.1.0/src/jrtc/core/logging/_json.py +96 -0
- jrtc-3.1.0/src/jrtc/core/logging/formatting.py +58 -0
- jrtc-3.1.0/src/jrtc/core/logging/utils.py +165 -0
- jrtc-3.1.0/src/jrtc/core/utils.py +29 -0
- jrtc-3.1.0/src/jrtc/lib/__init__.py +5 -0
- jrtc-3.1.0/src/jrtc/lib/manager.py +149 -0
- jrtc-3.1.0/src/jrtc/lib/plugins/__init__.py +0 -0
- jrtc-3.1.0/src/jrtc/lib/plugins/base.py +446 -0
- jrtc-3.1.0/src/jrtc/lib/registry.py +149 -0
- jrtc-3.1.0/src/jrtc/lib/utils.py +20 -0
- jrtc-3.1.0/src/jrtc/manager.py +297 -0
- jrtc-3.1.0/src/jrtc/messaging/__init__.py +57 -0
- jrtc-3.1.0/src/jrtc/messaging/constants.py +53 -0
- jrtc-3.1.0/src/jrtc/messaging/dispatcher.py +241 -0
- jrtc-3.1.0/src/jrtc/messaging/engines/__init__.py +9 -0
- jrtc-3.1.0/src/jrtc/messaging/engines/kafka.py +636 -0
- jrtc-3.1.0/src/jrtc/messaging/factory.py +117 -0
- jrtc-3.1.0/src/jrtc/messaging/listeners.py +122 -0
- jrtc-3.1.0/src/jrtc/messaging/metrics.py +115 -0
- jrtc-3.1.0/src/jrtc/messaging/publisher.py +544 -0
- jrtc-3.1.0/src/jrtc/models/__init__.py +4 -0
- jrtc-3.1.0/src/jrtc/models/base.py +27 -0
- jrtc-3.1.0/src/jrtc/models/common.py +71 -0
- jrtc-3.1.0/src/jrtc/models/request.py +156 -0
- jrtc-3.1.0/src/jrtc/models/response.py +197 -0
- jrtc-3.1.0/src/jrtc/py.typed +1 -0
- jrtc-3.1.0/src/jrtc/session/__init__.py +23 -0
- jrtc-3.1.0/src/jrtc/session/base.py +387 -0
- jrtc-3.1.0/src/jrtc/session/websocket.py +281 -0
- jrtc-3.1.0/src/jrtc/transport/__init__.py +22 -0
- jrtc-3.1.0/src/jrtc/transport/base.py +47 -0
- jrtc-3.1.0/src/jrtc/transport/http.py +490 -0
- jrtc-3.1.0/src/jrtc/transport/websocket.py +457 -0
- jrtc-3.1.0/src/jrtc.egg-info/PKG-INFO +535 -0
- jrtc-3.1.0/src/jrtc.egg-info/SOURCES.txt +57 -0
- jrtc-3.1.0/src/jrtc.egg-info/dependency_links.txt +1 -0
- jrtc-3.1.0/src/jrtc.egg-info/requires.txt +12 -0
- jrtc-3.1.0/src/jrtc.egg-info/top_level.txt +1 -0
- jrtc-3.1.0/tests/test_dependency_hygiene.py +61 -0
- jrtc-3.1.0/tests/test_kafka_engine.py +418 -0
- jrtc-3.1.0/tests/test_messaging.py +509 -0
- jrtc-3.1.0/tests/test_plugin_local_events.py +161 -0
- jrtc-3.1.0/tests/test_response_parser.py +34 -0
jrtc-3.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leydotpy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
jrtc-3.1.0/MANIFEST.in
ADDED
jrtc-3.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jrtc
|
|
3
|
+
Version: 3.1.0
|
|
4
|
+
Summary: Plugin-agnostic async Python core for the Janus WebRTC Gateway
|
|
5
|
+
Author-email: Leydotpy <leydotpy.dev@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://janus.conf.meetecho.com/
|
|
8
|
+
Project-URL: Documentation, https://janus.conf.meetecho.com/docs/
|
|
9
|
+
Project-URL: Repository, https://github.com/Leydotpy/Janus-API
|
|
10
|
+
Project-URL: Issues, https://github.com/Leydotpy/Janus-API/issues
|
|
11
|
+
Keywords: janus,webrtc,asyncio,websocket,media-server
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Communications
|
|
20
|
+
Classifier: Topic :: Internet
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: broka==0.0.2
|
|
26
|
+
Requires-Dist: dispio==0.0.2
|
|
27
|
+
Requires-Dist: logvista>=0.1.0
|
|
28
|
+
Requires-Dist: pydantic<3,>=2.10
|
|
29
|
+
Requires-Dist: websockets<17,>=14
|
|
30
|
+
Provides-Extra: http
|
|
31
|
+
Requires-Dist: httpx<1,>=0.27; extra == "http"
|
|
32
|
+
Requires-Dist: idna<4,>=3.15; extra == "http"
|
|
33
|
+
Provides-Extra: kafka
|
|
34
|
+
Requires-Dist: aiokafka<1,>=0.14; extra == "kafka"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# Janus Core
|
|
38
|
+
|
|
39
|
+
Production-oriented, async Python foundations for the
|
|
40
|
+
[Janus WebRTC Gateway](https://janus.conf.meetecho.com/): sessions, transports,
|
|
41
|
+
authentication, protocol envelopes, plugin-handle lifecycle, event routing, and
|
|
42
|
+
session-pool ownership. The separately installable operations server contains
|
|
43
|
+
the FastAPI, monitoring, administration, and persistence surfaces.
|
|
44
|
+
|
|
45
|
+
Janus Core intentionally contains **no named Janus plugin implementation**.
|
|
46
|
+
EchoTest, VideoCall, SIP, NoSIP, AudioBridge, VideoRoom, TextRoom, and
|
|
47
|
+
Record&Play are independent projects in `plugins/`; Streaming lives entirely
|
|
48
|
+
in `plugins/janus-streaming-plugin`. Applications can install only what
|
|
49
|
+
they use or implement their own client on the public `Plugin` base.
|
|
50
|
+
|
|
51
|
+
> The product is named **Janus Core**. The exact `janus-core` distribution and
|
|
52
|
+
> `janus_core` import namespace are already occupied on PyPI by an unrelated
|
|
53
|
+
> project, so this project uses the publishable distribution name
|
|
54
|
+
> `jrtc` and retains the stable `jrtc` Python namespace.
|
|
55
|
+
|
|
56
|
+
## Requirements and installation
|
|
57
|
+
|
|
58
|
+
- Python 3.12+
|
|
59
|
+
- Janus Gateway with at least one enabled API transport
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install jrtc
|
|
63
|
+
|
|
64
|
+
# Plain HTTP/HTTPS transport
|
|
65
|
+
pip install "jrtc[http]"
|
|
66
|
+
|
|
67
|
+
# Explicit Kafka client support for the core broker adapter
|
|
68
|
+
pip install "jrtc[kafka]"
|
|
69
|
+
|
|
70
|
+
# Independent FastAPI monitoring and administration service
|
|
71
|
+
pip install "japi[ops]"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The core runtime uses Pydantic, WebSockets, LogVista, Dispio, and Broka. Broka
|
|
75
|
+
0.0.2 currently declares its Redis, RabbitMQ, and Kafka clients as required
|
|
76
|
+
dependencies, even when an in-process engine is selected. HTTP transport is the
|
|
77
|
+
core transport extra; the Kafka extra explicitly declares the client used by
|
|
78
|
+
the hardened adapter instead of relying on Broka's transitive metadata.
|
|
79
|
+
`japi` directly depends on core but core never imports FastAPI,
|
|
80
|
+
Starlette, Uvicorn, asyncpg, or the server package.
|
|
81
|
+
|
|
82
|
+
### Migrating from the bundled operations server
|
|
83
|
+
|
|
84
|
+
The 3.1 package boundary is intentionally explicit:
|
|
85
|
+
|
|
86
|
+
- Replace `jrtc.create_asgi_app` and `jrtc.api` imports with
|
|
87
|
+
`japi.create_asgi_app` and `japi.api`.
|
|
88
|
+
- Replace `jrtc.contrib.admin.db.migrate` with
|
|
89
|
+
`japi.contrib.admin.db.migrate`.
|
|
90
|
+
- Import `JanusSessionManager` from `jrtc` (or `jrtc.session`). The
|
|
91
|
+
former mixed `jrtc.servers` namespace is gone.
|
|
92
|
+
- Remove callers of the former `/manager` API. The operations service neither
|
|
93
|
+
owns nor exposes application session pools.
|
|
94
|
+
- Configure EventHandler delivery with `JANUS_EVENT_BROKER_*` settings. The
|
|
95
|
+
legacy Kafka topic variable is accepted only as a destination fallback.
|
|
96
|
+
|
|
97
|
+
EventHandler records are now delivered using Broka's envelope wire format,
|
|
98
|
+
not the previous raw-JSON Kafka value. Native Kafka consumers must decode the
|
|
99
|
+
Broka envelope and deduplicate on its stable `janus-event-id` header; they must
|
|
100
|
+
not use the transport-generated envelope ID as the retry key. Migrate consumers
|
|
101
|
+
before routing an existing production topic through the new server.
|
|
102
|
+
|
|
103
|
+
The old operations flags are not read by the new distribution. Rename them as
|
|
104
|
+
part of the same deployment:
|
|
105
|
+
|
|
106
|
+
| Previous variable | Server variable |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `JANUS_MOUNT_REST_API` | `JANUS_SERVER_MOUNT_REST_API` |
|
|
109
|
+
| `JANUS_ENABLE_ADMIN` | `JANUS_SERVER_ENABLE_ADMIN` |
|
|
110
|
+
| `JANUS_ENABLE_EVENTS` | `JANUS_SERVER_ENABLE_EVENTS` |
|
|
111
|
+
| `JANUS_MOUNT_LOGGING_APP` | `JANUS_SERVER_MOUNT_LOGGING_APP` |
|
|
112
|
+
| `JANUS_ALLOWED_ORIGINS` | `JANUS_SERVER_ALLOWED_ORIGINS` |
|
|
113
|
+
| `jrtc_ALLOW_CREDENTIALS` | `JANUS_SERVER_API_ALLOW_CREDENTIALS` |
|
|
114
|
+
|
|
115
|
+
## Client quick start
|
|
116
|
+
|
|
117
|
+
Install only the named plugin an application needs:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install janus-echotest-plugin
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import asyncio
|
|
125
|
+
|
|
126
|
+
from jrtc import JanusSession
|
|
127
|
+
from janus_echotest_plugin import EchoTestPlugin
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async def main() -> None:
|
|
131
|
+
async with JanusSession(url="ws://127.0.0.1:8188/janus") as session:
|
|
132
|
+
async with EchoTestPlugin(session=session) as echo:
|
|
133
|
+
reply = await echo.configure(audio=True, video=False)
|
|
134
|
+
print(reply.data)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
asyncio.run(main())
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Use the same session API with Janus REST; long polling and REST path addressing
|
|
141
|
+
are managed by the HTTP transport:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
async with JanusSession(url="http://127.0.0.1:8088/janus") as session:
|
|
145
|
+
...
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
WebSocket disconnects invalidate every session and handle bound to that socket.
|
|
149
|
+
The manager creates fresh sessions rather than silently reusing stale Janus IDs.
|
|
150
|
+
Requests have bounded transaction tables and explicit timeouts; cancellation
|
|
151
|
+
always releases the pending transaction. Shutdown detaches handles with bounded
|
|
152
|
+
concurrency, reserves time for the Janus session destroy, and completes local
|
|
153
|
+
cleanup even if the calling task is cancelled.
|
|
154
|
+
|
|
155
|
+
WebSocket and HTTP are the built-in transports. RabbitMQ, MQTT, nanomsg, Unix
|
|
156
|
+
sockets, or an application-specific Janus transport can implement the small
|
|
157
|
+
`JanusTransport` protocol and be injected without core interpreting its URL:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from jrtc import JanusSession
|
|
161
|
+
|
|
162
|
+
session = JanusSession(
|
|
163
|
+
transport=my_transport,
|
|
164
|
+
url="unix:///run/janus.sock", # owned by the injected transport
|
|
165
|
+
)
|
|
166
|
+
await session.create()
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Pass either `transport` or `transport_factory`, never both. A factory-created
|
|
170
|
+
transport is owned and closed by the session; a directly injected transport is
|
|
171
|
+
treated as shared and remains owned by the host application.
|
|
172
|
+
|
|
173
|
+
## Brokered WebRTC events
|
|
174
|
+
|
|
175
|
+
Inbound responses are coordinated once by a frozen Dispio dispatcher. ACK,
|
|
176
|
+
error, and transaction responses stay local; asynchronous Janus responses are
|
|
177
|
+
admitted directly from either transport to a bounded, ordered Broka publisher.
|
|
178
|
+
They do not pass through plugin callbacks on their way to third-party
|
|
179
|
+
applications, and ReactiveX is not part of the runtime.
|
|
180
|
+
|
|
181
|
+
Every event uses a logical type such as `janus.event` or `janus.media`, mapped
|
|
182
|
+
to one portable physical destination, `janus.events`. JSEP remains embedded in
|
|
183
|
+
the complete `janus.event` payload—there is no second SDP event.
|
|
184
|
+
|
|
185
|
+
The application that owns the Janus session also owns and injects its publisher.
|
|
186
|
+
The operations server deliberately creates neither one, which prevents duplicate
|
|
187
|
+
Janus sessions when both packages run in the same process:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from jrtc import JanusSession
|
|
191
|
+
from jrtc.messaging import JanusEventPublisher, create_broker
|
|
192
|
+
|
|
193
|
+
broker = create_broker(
|
|
194
|
+
engine="redis",
|
|
195
|
+
engine_options={
|
|
196
|
+
"mode": "streams",
|
|
197
|
+
"url": "redis://localhost:6379/0",
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
async with JanusEventPublisher(broker) as publisher:
|
|
202
|
+
async with JanusSession(event_publisher=publisher) as session:
|
|
203
|
+
...
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Third-party applications subscribe to the exact physical destination and route
|
|
207
|
+
on `delivery.envelope.type`:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from jrtc.messaging import DEFAULT_PHYSICAL_ROUTE, create_broker
|
|
211
|
+
|
|
212
|
+
subscriber = create_broker(
|
|
213
|
+
engine="redis",
|
|
214
|
+
engine_options={
|
|
215
|
+
"mode": "streams",
|
|
216
|
+
"url": "redis://localhost:6379/0",
|
|
217
|
+
"group": "analytics-janus-v1",
|
|
218
|
+
"consumer_name": "analytics-01",
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
async def receive(delivery) -> None:
|
|
224
|
+
logical_type = delivery.envelope.type
|
|
225
|
+
response = delivery.message
|
|
226
|
+
jsep = response.get("jsep")
|
|
227
|
+
await application_events.handle(logical_type, response, jsep=jsep)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async with subscriber:
|
|
231
|
+
subscription = await subscriber.subscribe(DEFAULT_PHYSICAL_ROUTE, receive)
|
|
232
|
+
try:
|
|
233
|
+
await stop_event.wait()
|
|
234
|
+
finally:
|
|
235
|
+
await subscription.close()
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Redis Pub/Sub, Redis Streams, RabbitMQ, Kafka, native wire consumers,
|
|
239
|
+
acknowledgement semantics, stable consumer identities, and current upstream
|
|
240
|
+
adapter limits are covered in the
|
|
241
|
+
[broker event guide](https://github.com/Leydotpy/Janus-API/blob/main/docs/broker-events.md).
|
|
242
|
+
|
|
243
|
+
## Authentication
|
|
244
|
+
|
|
245
|
+
Janus API tokens and API secrets belong to the outer envelope of every request.
|
|
246
|
+
Configure them once per session; representations and settings inspection redact
|
|
247
|
+
their values.
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from jrtc import JanusCredentials, JanusSession
|
|
251
|
+
|
|
252
|
+
credentials = JanusCredentials(token="client-token", api_secret="shared-secret")
|
|
253
|
+
|
|
254
|
+
async with JanusSession(credentials=credentials) as session:
|
|
255
|
+
...
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
A callable returning `JanusCredentials` may be supplied for credential rotation.
|
|
259
|
+
The default session manager reads `JANUS_TOKEN` and `jrtc_SECRET`.
|
|
260
|
+
|
|
261
|
+
## Plugin projects
|
|
262
|
+
|
|
263
|
+
The sibling `plugins/` workspace contains independent distributions:
|
|
264
|
+
|
|
265
|
+
| Janus plugin | Distribution | Import package | Entry-point name |
|
|
266
|
+
|---|---|---|---|
|
|
267
|
+
| EchoTest | `janus-echotest-plugin` | `janus_echotest_plugin` | `echotest` |
|
|
268
|
+
| VideoCall | `janus-videocall-plugin` | `janus_videocall_plugin` | `videocall` |
|
|
269
|
+
| SIP | `janus-sip-plugin` | `janus_sip_plugin` | `sip` |
|
|
270
|
+
| NoSIP | `janus-nosip-plugin` | `janus_nosip_plugin` | `nosip` |
|
|
271
|
+
| AudioBridge | `janus-audiobridge-plugin` | `janus_audiobridge_plugin` | `audiobridge` |
|
|
272
|
+
| VideoRoom | `janus-videoroom-plugin` | `janus_videoroom_plugin` | `videoroom` |
|
|
273
|
+
| TextRoom | `janus-textroom-plugin` | `janus_textroom_plugin` | `textroom` |
|
|
274
|
+
| Record&Play | `janus-recordplay-plugin` | `janus_recordplay_plugin` | `recordplay` |
|
|
275
|
+
|
|
276
|
+
Streaming remains in the `plugins/janus-streaming-plugin`
|
|
277
|
+
workspace as distribution `janus-api-streaming`, import package
|
|
278
|
+
`janus_streaming`, and entry-point name `streaming`.
|
|
279
|
+
|
|
280
|
+
Installed plugins are discovered lazily through the `jrtc.plugins` entry
|
|
281
|
+
point group. Importing Janus Core never scans or executes arbitrary local files
|
|
282
|
+
and never imports unrelated named plugins.
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
from jrtc.lib import Plugin
|
|
286
|
+
|
|
287
|
+
# Resolves only the installed `echotest` entry point.
|
|
288
|
+
echo = Plugin(identifier="echotest", session=session)
|
|
289
|
+
await echo.attach()
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Direct construction of the concrete class is preferred because it gives type
|
|
293
|
+
checkers the plugin-specific methods and result types.
|
|
294
|
+
|
|
295
|
+
## Implementing a custom plugin
|
|
296
|
+
|
|
297
|
+
Only the generic base and shared protocol primitives are required:
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
from typing import Literal
|
|
301
|
+
|
|
302
|
+
from pydantic import BaseModel
|
|
303
|
+
|
|
304
|
+
from jrtc.lib import Plugin
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class StatusRequest(BaseModel):
|
|
308
|
+
request: Literal["status"] = "status"
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class MyPlugin(Plugin):
|
|
312
|
+
identifier = "my-plugin"
|
|
313
|
+
name = "janus.plugin.my-plugin"
|
|
314
|
+
|
|
315
|
+
async def status(self):
|
|
316
|
+
return await self.send(StatusRequest())
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
To support lazy discovery from a separate distribution:
|
|
320
|
+
|
|
321
|
+
```toml
|
|
322
|
+
[project.entry-points."jrtc.plugins"]
|
|
323
|
+
my-plugin = "my_package.plugin:MyPlugin"
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Plugin response bodies are opaque to core. A plugin package should use strict
|
|
327
|
+
outbound models, forward-compatible inbound models, typed plugin errors, and
|
|
328
|
+
golden protocol tests derived from its Janus documentation.
|
|
329
|
+
|
|
330
|
+
## Session ownership and the separate operations server
|
|
331
|
+
|
|
332
|
+
`JanusSessionManager` remains part of `jrtc` and owns a bounded,
|
|
333
|
+
process-local session pool. Each application owns its Janus connections and
|
|
334
|
+
passes the selected session to its own plugins. The operations server never
|
|
335
|
+
creates, installs, or tears down a session or manager.
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
from jrtc import JanusSessionManager
|
|
339
|
+
|
|
340
|
+
async with JanusSessionManager(pool_size=2) as manager:
|
|
341
|
+
session = manager.get_session(key="tenant-42")
|
|
342
|
+
if session is None:
|
|
343
|
+
raise RuntimeError("Janus is unavailable")
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Install `japi` independently for monitoring and administration. Its
|
|
347
|
+
ASGI lifespan owns only server resources such as the Admin monitor, optional
|
|
348
|
+
Timescale storage, EventHandler broker sink, and log tailer:
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
from japi import create_asgi_app
|
|
352
|
+
|
|
353
|
+
app = create_asgi_app(mount_rest_api=True)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
JANUS_SERVER_MOUNT_REST_API=true \
|
|
358
|
+
uvicorn myapp:app --host 0.0.0.0 --port 8000
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
When enabled, the `/janus` mount can expose:
|
|
362
|
+
|
|
363
|
+
- `/admin/` — authenticated Admin/Monitor JSON, Prometheus, and WebSocket APIs
|
|
364
|
+
- `/events/janus-events` — bounded, Basic-authenticated EventHandler ingestion to Broka
|
|
365
|
+
- `/logs/` and `/logs/ws/logs` — bounded API-key-authenticated structured log access
|
|
366
|
+
|
|
367
|
+
There is intentionally no manager route. Monitoring the operations process and
|
|
368
|
+
managing application-owned Janus sessions are separate concerns.
|
|
369
|
+
|
|
370
|
+
The Admin surface is deliberately API-first (JSON, Prometheus text, and
|
|
371
|
+
WebSocket updates); core does not ship raw, uncompiled frontend source as a
|
|
372
|
+
production UI.
|
|
373
|
+
|
|
374
|
+
Admin, EventHandler, log viewer, and Timescale resources are disabled by
|
|
375
|
+
default. The service fails closed when an enabled component lacks credentials.
|
|
376
|
+
Schema migration is an explicit deployment action, never an import/startup side
|
|
377
|
+
effect:
|
|
378
|
+
|
|
379
|
+
```python
|
|
380
|
+
import asyncio
|
|
381
|
+
|
|
382
|
+
from japi.contrib.admin.db import migrate
|
|
383
|
+
|
|
384
|
+
asyncio.run(migrate())
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
EventHandler delivery uses the Broka abstraction with a stable
|
|
388
|
+
`janus-event-id` header, bounded per-application admission, finite publish and
|
|
389
|
+
batch deadlines, and partition/ordering keys derived from the Janus session or
|
|
390
|
+
emitter. Kafka deployments use the hardened core adapter, which forwards an
|
|
391
|
+
explicit allowlist of TLS/SASL and client options and requests idempotent,
|
|
392
|
+
`acks=all` delivery. Consumers should deduplicate by `janus-event-id` because an
|
|
393
|
+
HTTP batch can partially succeed before Janus retries it. Timescale queries have
|
|
394
|
+
statement, row, and time-bucket budgets; optional retention and compression
|
|
395
|
+
policies are installed only by the explicit migration.
|
|
396
|
+
|
|
397
|
+
Structured file logging is also opt-in. The handler installer is idempotent,
|
|
398
|
+
recursively redacts nested credentials, creates restrictive files, and supports
|
|
399
|
+
size-based or external watched rotation:
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
from jrtc.core.logging import install_colored_logging
|
|
403
|
+
|
|
404
|
+
install_colored_logging(logfile="/var/log/myapp/janus.jsonl", rotation="watched")
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Core configuration
|
|
408
|
+
|
|
409
|
+
Configure these values through the host application's environment or secret
|
|
410
|
+
manager. The package itself deliberately does not load `.env` files.
|
|
411
|
+
|
|
412
|
+
| Variable | Default | Purpose |
|
|
413
|
+
|---|---:|---|
|
|
414
|
+
| `JANUS_SESSION_URL` | `ws://localhost:8188/janus` | WS/WSS or HTTP/HTTPS API endpoint |
|
|
415
|
+
| `JANUS_REQUEST_TIMEOUT` | `15` | Per-request timeout in seconds |
|
|
416
|
+
| `JANUS_SESSION_POOL_SIZE` | `1` | Sessions per process |
|
|
417
|
+
| `JANUS_KEEPALIVE_INTERVAL` | `25` | WebSocket session keepalive interval |
|
|
418
|
+
| `JANUS_KEEPALIVE_FAILURES` | `3` | Failures before a session is invalidated |
|
|
419
|
+
| `JANUS_SHUTDOWN_TIMEOUT` | `10` | Total bounded session-shutdown budget |
|
|
420
|
+
| `JANUS_DETACH_CONCURRENCY` | `16` | Concurrent handle detach limit |
|
|
421
|
+
| `JANUS_TOKEN` | unset | Janus token authentication |
|
|
422
|
+
| `jrtc_SECRET` | unset | Janus shared API secret |
|
|
423
|
+
| `JANUS_BROKER_ENGINE` | `memory` | `memory`, `local`, `redis`, `rabbitmq`, or `kafka` |
|
|
424
|
+
| `JANUS_BROKER_ROUTE` | `janus.events` | Exact physical event destination |
|
|
425
|
+
| `JANUS_BROKER_ENGINE_OPTIONS` | `{}` | JSON object passed to the selected Broka engine |
|
|
426
|
+
| `JANUS_BROKER_OPTIONS` | `{}` | JSON object passed to Broka configuration |
|
|
427
|
+
| `JANUS_BROKER_PUBLISH_WORKERS` | `4` | Ordered publisher worker shards |
|
|
428
|
+
| `JANUS_BROKER_QUEUE_CAPACITY` | `4096` | Process-wide bounded event admission |
|
|
429
|
+
| `JANUS_BROKER_ADMISSION_TIMEOUT` | `0.05` | Maximum queue-admission wait in seconds |
|
|
430
|
+
| `JANUS_BROKER_PUBLISH_TIMEOUT` | `5` | Hard deadline for each backend publication |
|
|
431
|
+
| `JANUS_BROKER_DRAIN_TIMEOUT` | `10` | Publisher shutdown/drain budget in seconds |
|
|
432
|
+
|
|
433
|
+
Use a custom typed module through `JANUS_SETTINGS_MODULE` for more complex core
|
|
434
|
+
deployments; explicit overrides are available through `jrtc.conf.configure`.
|
|
435
|
+
|
|
436
|
+
## Operations server configuration
|
|
437
|
+
|
|
438
|
+
The server loads its own settings independently through
|
|
439
|
+
`JANUS_SERVER_SETTINGS_MODULE` or `japi.configure`. Important
|
|
440
|
+
defaults are:
|
|
441
|
+
|
|
442
|
+
| Variable | Default | Purpose |
|
|
443
|
+
|---|---:|---|
|
|
444
|
+
| `JANUS_SERVER_MOUNT_REST_API` | `true` | Mount the REST application at `/janus` |
|
|
445
|
+
| `JANUS_SERVER_ENABLE_ADMIN` | `false` | Start Admin/Monitor integration |
|
|
446
|
+
| `JANUS_SERVER_ENABLE_EVENTS` | `false` | Start EventHandler broker ingestion |
|
|
447
|
+
| `JANUS_SERVER_MOUNT_LOGGING_APP` | `false` | Enable structured log query/streaming |
|
|
448
|
+
| `JANUS_SERVER_ALLOWED_ORIGINS` | unset | Exact CORS/WebSocket origins |
|
|
449
|
+
| `JANUS_SERVER_LIFESPAN_STARTUP_TIMEOUT` | `30` | Deadline for each external-resource startup |
|
|
450
|
+
| `JANUS_SERVER_LIFESPAN_SHUTDOWN_TIMEOUT` | `30` | Deadline for each external-resource shutdown |
|
|
451
|
+
| `JANUS_EVENT_BROKER_ENGINE` | unset | Required EventHandler Broka backend when ingestion is enabled |
|
|
452
|
+
| `JANUS_EVENT_BROKER_ENGINE_OPTIONS` | `{}` | Backend options, including secured Kafka options |
|
|
453
|
+
| `JANUS_EVENT_BROKER_OPTIONS` | `{}` | Broka reliability/serialization options |
|
|
454
|
+
| `JANUS_EVENT_HANDLER_LOGICAL_ROUTE` | `janus.eventhandler` | Logical Broka envelope type |
|
|
455
|
+
| `JANUS_EVENT_HANDLER_DESTINATION` | `janus.eventhandler` | Physical backend destination |
|
|
456
|
+
| `JANUS_EVENT_HANDLER_DELIVERY_CONCURRENCY` | `32` | Per-process concurrent deliveries |
|
|
457
|
+
| `JANUS_EVENT_HANDLER_MAX_INFLIGHT_BATCHES` | `16` | Per-process HTTP batch admission bound |
|
|
458
|
+
| `JANUS_EVENT_HANDLER_ADMISSION_TIMEOUT` | `0.1` | Capacity-admission deadline in seconds |
|
|
459
|
+
| `JANUS_EVENT_HANDLER_PUBLISH_TIMEOUT` | `5` | Per-event broker deadline in seconds |
|
|
460
|
+
| `JANUS_EVENT_HANDLER_BATCH_TIMEOUT` | `30` | Whole-batch deadline in seconds |
|
|
461
|
+
| `JANUS_TIMESCALE_QUERY_TIMEOUT` | `5` | Aggregate query/command timeout |
|
|
462
|
+
| `JANUS_TIMESCALE_MAX_QUERY_ROWS` | `10000` | Hard aggregate-result row limit |
|
|
463
|
+
|
|
464
|
+
Admin, EventHandler, log-viewer, and persistence credentials have no fallback
|
|
465
|
+
values. Enabled services fail startup when their required secrets are absent.
|
|
466
|
+
Redis Streams, RabbitMQ, or Kafka should back durable EventHandler ingestion;
|
|
467
|
+
Redis Pub/Sub is rejected because it cannot provide at-least-once delivery.
|
|
468
|
+
|
|
469
|
+
Scale the operations surfaces independently. EventHandler-only replicas are
|
|
470
|
+
stateless apart from bounded process-local admission and can scale horizontally
|
|
471
|
+
against one shared broker destination. Janus Admin polling, Timescale fallback
|
|
472
|
+
sampling, and file tailing are singleton activities for each Janus target or
|
|
473
|
+
log file: run those flags in a one-worker deployment (for example,
|
|
474
|
+
`uvicorn ... --workers 1`) or behind an external leader-election mechanism.
|
|
475
|
+
Enabling them in every worker would duplicate polling and persistence, while a
|
|
476
|
+
WebSocket can observe only the worker to which it is connected. Separate
|
|
477
|
+
event-only and admin/log deployments when both ingestion throughput and
|
|
478
|
+
monitoring availability need to scale.
|
|
479
|
+
|
|
480
|
+
## Development and verification
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
uv sync --all-packages --all-extras --group dev
|
|
484
|
+
uv run pytest
|
|
485
|
+
uv run ruff check src tests packages/japi/src packages/japi/tests
|
|
486
|
+
uv run ruff format --check src tests packages/japi/src packages/japi/tests
|
|
487
|
+
uv run mypy src/jrtc packages/japi/src/japi
|
|
488
|
+
uv build --package jrtc
|
|
489
|
+
uv build --package japi
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
The tracked tests cover response validation, local plugin lifecycle, complete
|
|
493
|
+
ReactiveX removal, bounded publisher concurrency and draining, real Broka
|
|
494
|
+
memory delivery, Dispio response selection, and one-envelope JSEP behavior for
|
|
495
|
+
both built-in transports. Live Janus and external broker tests should also run
|
|
496
|
+
against the exact versions used in each deployment.
|
|
497
|
+
|
|
498
|
+
See the
|
|
499
|
+
[broker event guide](https://github.com/Leydotpy/Janus-API/blob/main/docs/broker-events.md)
|
|
500
|
+
for ownership, event contracts, backend subscription topology, reliability,
|
|
501
|
+
and deployment limits.
|
|
502
|
+
|
|
503
|
+
## 3.1 messaging migration
|
|
504
|
+
|
|
505
|
+
- ReactiveX transport/plugin APIs were removed. Local plugin callbacks use
|
|
506
|
+
bounded instance-owned queues; cross-process subscribers use Broka.
|
|
507
|
+
- Dispio now coordinates ACK, error, transaction, and asynchronous response
|
|
508
|
+
handling without a transport `if`/`elif` decision tree.
|
|
509
|
+
- JSEP is delivered only inside its original response and is never dispatched
|
|
510
|
+
as a separate SDP event.
|
|
511
|
+
- Python 3.12 or newer is required by the pinned messaging dependencies.
|
|
512
|
+
|
|
513
|
+
## 2.x migration notes
|
|
514
|
+
|
|
515
|
+
- Distribution: `janus-api` → `jrtc`; import namespace remains `jrtc`.
|
|
516
|
+
- `WebsocketSession` remains an alias of the transport-agnostic `JanusSession`.
|
|
517
|
+
- Named plugin models, clients, and the old VideoRoom facade moved out of core.
|
|
518
|
+
- Streaming moved completely to `janus-api-streaming` under `plugins`.
|
|
519
|
+
- Sessions and plugin managers are ordinary instances; process-global singleton
|
|
520
|
+
handles, global Rx event routing, eager plugin scanning, and Redis RPC are gone.
|
|
521
|
+
- Plugin payloads are no longer part of a closed core request/response union.
|
|
522
|
+
- Root logging, database migrations, threads, sockets, and optional-service
|
|
523
|
+
imports no longer happen at module import time.
|
|
524
|
+
|
|
525
|
+
## Protocol references
|
|
526
|
+
|
|
527
|
+
- [Janus transports and core protocol](https://janus.conf.meetecho.com/docs/rest.html)
|
|
528
|
+
- [Janus API authentication](https://janus.conf.meetecho.com/docs/auth.html)
|
|
529
|
+
- [Admin/Monitor API](https://janus.conf.meetecho.com/docs/admin.html)
|
|
530
|
+
- [Event handlers](https://janus.conf.meetecho.com/docs/eventhandlers.html)
|
|
531
|
+
- [Recordings](https://janus.conf.meetecho.com/docs/recordings.html)
|
|
532
|
+
|
|
533
|
+
## License
|
|
534
|
+
|
|
535
|
+
MIT
|